QiShare文章汇总

Swift 5.1 (17) - 类型转换与模式匹配

2020-05-14  本文已影响0人  QiShare

级别: ★☆☆☆☆
标签:「iOS」「Swift 5.1 」「is」「as」「pattern」「any」「anyObject」
作者: 沐灵洛
审校: QiShare团队


类型转换在Swift中使用isas操作符实现。

类型检查

使用操作符is检查一个实例是否是某个确定的类以及其继承体系的父类或子类类型。如果是某个确定的类(该类继承体系的父类或子类)类型,则返回true,否则返回false

class Cat {
    func hairColor() -> String {
        return "五颜六色"
    }
}
class WhiteCat: Cat {
    override func hairColor() -> String {
        return "白色"
    }
}
class BlackCat: Cat {
    override func hairColor() -> String {
        return "黑色"
    }
}
//必须符合`Cat`类以及其子类,类型推断需要
let kinds = [WhiteCat(),BlackCat(),WhiteCat(),WhiteCat()]
for item in kinds {
    if item is WhiteCat {
        print("白猫")//!< 3次
    }
    if item is BlackCat {
        print("黑猫")//!< 1次
    }
    if item is Cat {
        print("猫")//!< 4次
    }
}

向下转换

某个类类型的常量或变量实际上可能是其子类的实例。这种情况下,我们会用到类型转换操作符(as?as!)向下转换为子类类型。
as?:类型转换的条件形式,向下转换为某个类型时,返回该类型的可选值,即:转换失败时返回nil。使用场景:向下转换可能会失败的情况。
as!:类型转换的强制形式,向下转换为某个类型时,会进行强制解包,即:转换失败时触发运行时错误。使用场景:向下转换确定不会失败

//必须符合`Cat`类以及其子类,类型推断需要
let kinds = [WhiteCat(),BlackCat(),WhiteCat(),WhiteCat()]
for item in kinds {
    if let white = item as? WhiteCat {
        print("毛发:\(white.hairColor())")
    }
    if let black = item as? BlackCat {
        print("毛发:\(black.hairColor())")
    }
}

下述内容总结自 苹果官方博客:
Swift 1.2之前as运算符可以执行两种不同类型的转换:保证转换和强制转换。
保证转换:保证将一种类型的值转换为另一种类型,这种保证由编译器编译时验证。
例如:
• 向上转换(Upcasting),将当前类型的值转换为该类型的父类之一。
• 指定数字的类型:let num = 6 as Float
强制转换:强制将一种类型的值转换为另一种类型,这种转换编译器无法保证安全性,并且可能触发运行时错误。
例如:上述的向下转换(Downcasting),将一种类型的值转换为其子类之一。
在Swift 1.2之后保证转换仍然使用as操作符,但强制转换使用as!操作符。

AnyAnyObject的类型转换

Swift提供了两种特殊类型来处理非特定类型:

在某些使用anyAnyObject的特殊场景下,对于AnyAnyObject表示的实例,需要运用类型转换模式,值绑定模式,表达式模式等模式匹配的知识。所以我们先介绍下Swift中的模式。

类型转换模式

类型转换有两种模式:is模式和as模式。is模式仅在switch语句的case标签中使用。is模式和as模式有如下形式:

is <#Type#>
//pattern:代表此处也需要一个模式
<#pattern#> as <#Type#>

is模式:如果运行时值的类型与is模式右侧指定的类型或该类型的子类相同,则is模式会匹配到这个值。此行为很适用switch语句的case场景。is模式的行为类似于is运算符,因为它们都执行类型转换但类型转换后丢弃了返回的类型。

as模式:如果在运行时值的类型与as模式右侧指定的类型或该类型的子类相同,则as模式会匹配到这个值。如果匹配成功,则会将匹配到的值的类型将转换为as模式右侧指定的类型。

值绑定模式

值绑定模式将匹配到的值绑定到变量或常量。
将匹配到的值绑定到常量,绑定模式以let关键字开头;绑定到变量以var关键字开头。

let point = (3,2)
switch point {
case let(x,y):
    //值绑定模式匹配到的X值:3,Y值:2
    print("值绑定模式匹配到的X值:\(x),Y值:\(y)")
}

通配符模式

通配符模式匹配并忽略任何值,并由下划线_表示。

for _ in 1...9 {
    print("通配符模式")
}

标识符模式

标识符模式匹配任何值,并将匹配的值绑定到变量或常量的名称。

let someValue = 42

someValue是一个与Int类型的值42匹配的标识符模式。匹配成功,42将被赋值给常量someValue
当变量或常量声明的左侧的模式是标识符模式时,标识符模式隐式地是值绑定模式的子模式。

元组模式

元组模式是以逗号分隔的零个或多个元素列表,括在括号中。元组模式匹配相应元组类型的值。
包含单个元素的元组模式周围的括号无效。该模式匹配该单个元素类型的值。所以下面写法是等效的:

let a = 2        // a: Int = 2
let (a) = 2      // a: Int = 2
let (a): Int = 2 // a: Int = 2

枚举Case模式

枚举Case模式匹配现有枚举中存在case。枚举Case模式出现在switch语句的case标签中以及ifwhileguardfor-in 语句中。
如果尝试匹配的枚举case具有关联值,则相应的枚举Case模式必须指定与每个关联值对应的元组。

enum VendingMachineError {
    case InvalidGoods//!< 商品无效
    case StockInsufficient//!< 库存不足
    case CoinInsufficient(coinNeeded:Int,caseDes:String)
}
let enumArray = [VendingMachineType.CoinInsufficient(coinNeeded: 4, caseDes: "自动售货机,硬币不足,请补充"),
                 .InvalidGoods,
                 .StockInsufficient,
                 .CoinInsufficient(coinNeeded: 6, caseDes: "自动售货机,硬币不足,超过限额")]
for patternCase in enumArray {
    switch patternCase {
    case .CoinInsufficient(coinNeeded: let x, caseDes: let y) where x > 5:
        print(x,y)
    case let .CoinInsufficient(coinNeeded: x, caseDes: y):
        print(x,y)
    case .InvalidGoods:
        print("商品无效")
    default:
        print("未匹配到")
    }
}

枚举Case模式还匹配枚举类型的可选项。当可选项Optional是枚举类型时,.none.some 能够作为枚举类型的其他case出现在同一个switch语句中。这种简化的语法允许我们省略可选模式。

enum SomeEnum { case left, right,top,down}
let array : Array<SomeEnum?> = [.left,nil,.right,.top,.down]
//方式一:
array.forEach { (item) in
    switch item {
    case .left?:
        print("左")
    case SomeEnum.right?:
        print("右")
    case .down?:
        print("下")
    case .top?:
        print("上")
    default:
        print("没有值")
    }
}
//方式二:
array.forEach { (item) in
    switch item {
    case .some(let x):
        print("对可选项item进行解包得到:\(x)")//!< left,right,top,down
    case .none:
        print("没有值") //nil
    }
}

可选模式

可选模式匹配包含在Optional<Wrapped>枚举(这是可选项的实现原理)对应的case项:some(Wrapped)中的值。即匹配可选项有值的情况。

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    /// The absence of a value.
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none
    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)
    ......
}

可选模式由标识符模式组成后面紧跟?并出现在与枚举Case模式相同的位置。
因为可选模式是Optional<Wrapped>枚举的Case模式语法糖。所以下面两种写法是等效的:

let someInt : Int? = 42
//方式一:枚举case模式
if case let .some(x) = someInt {
    print(x)
}
if case  .some(let x) = someInt {
    print(x)
}
//方式二:可选模式
if case let x? = someInt {
    print(x)
}

使用可选模式迭代包含可选项的数组是很方便的:

enum SomeEnum { case left, right,top,down}
let array : Array<SomeEnum?> = [.left,nil,.right,nil,.top,.down]
for case let item? in array {
    print(item)//!< log:left right top down
}
for case let .some(item) in array {
    print(item)//!< log:left right top down
}
for case .some(let item) in array {
    print(item)//!< log:left right top down
}

表达式模式

表达式模式:表示表达式的值,仅出现在switch语句的case标签中。
表达式模式的机制:使用Swift标准库中的~=操作符将表达式模式中表达式的值与匹配值(输入值)进行比较,若~=返回true则证明匹配成功,否则匹配失败。
~=运算符默认情况下使用==运算符比较两个相同类型的值;也可以通过检查某个值是否在某个范围内来匹配范围值。

let point = (9,14)
switch point {
case (9,14):
    print("表达式模式使用`~=`精准匹配::(\(point.0),\(point.1))")
    fallthrough
case (5..<10,0...20):
    print("表达式模式使用`~=`范围匹配:(\(point.0),\(point.1))")
default:
    print("未匹配")
}

可以重载〜=运算符提供自定义表达式匹配行为:

//全局声明:class外部,否则报错
func ~= (pattern: String, value: Int) -> Bool {
    return pattern == "\(value)"
}
let point = (9,14)
switch point {
case ("9","14")://若不重载则会报错
    print("表达式模式使用`~=`精准匹配:(\(point.0),\(point.1))")
    fallthrough
case (5..<10,0...20):
    print("表达式模式使用`~=`范围匹配:(\(point.0),\(point.1))")
default:
    print("未匹配")
}

介绍完模式,接下来我们举例来说明模式在AnyAnyObject的类型转换的使用。
示例一:

var things : [Any] = [0, 0.0, 42, 3.14159, "hello", (3.0, 5.0),
                      WhiteCat(),{ (name: String) -> String in "Hello, \(name)" } ]
for thing in things {
    switch thing {
    case 0 as Int:
        print("`as`模式匹配两部分,pattern:表达式模式(`0`),type:匹配类型(`Int`),匹配结果:0")
    case (0) as Double:
        print("`as`模式匹配两部分,pattern:表达式模式(`0`),type:匹配类型(`Double`),匹配结果:0.0")
    case is Double:
        print("`is`模式匹配`Double`类型的值,值类型与`is`右侧类型及子类相同时,执行此句")
    case let someInt as Int:
        print("`as`模式匹配两部分,pattern:值绑定模式(`let someInt`),type:匹配类型(`Int`),匹配结果:\(someInt)")
    case _ as Int:
        print("`as`模式匹配两部分,pattern:通配符模式(`_`),type:匹配类型(`Int`),匹配结果被忽略")
    case let someDouble as Double where someDouble > 0:
        print("`as`模式匹配两部分,pattern:值绑定模式(`let someDouble`),type:匹配类型(`Double`),匹配结果:\(someDouble)")
    case let someString as String:
        print("`as`模式匹配两部分,pattern:值绑定模式(`let someString`),type:匹配类型(`String`),匹配结果:\(someString)")
    case let (x, y) as (Double, Double):
        print("`as`模式匹配两部分,pattern:元组模式(`let (x, y) `),type:匹配类型(元组`(Double, Double)`),匹配结果:\((x, y))")
        fallthrough
    case (2.0...4.0, 3.0...6.0) as (Double, Double):
        print("`as`模式匹配两部分,pattern:表达式模式(`(2.0...4.0, 3.0...6.0) `),type:匹配类型(元组`(Double, Double)`))")
    case let cat as WhiteCat:
        print("`as`模式匹配两部分,pattern:值绑定模式(`let cat`),type:匹配类型(对象`WhiteCat`),匹配结果:\(cat)")
    case let sayHelloFunc as (String) -> String:
        print("`as`模式匹配两部分,pattern:值绑定模式(`let sayHelloFunc`),type:匹配类型(函数`(String) -> String`),匹配结果:\(sayHelloFunc("QiShare"))")
    default:
        print("其他结果,未匹配到")
    }
}

示例二:

let point = (9,14)
switch point {
case (9,14):
    print("表达式模式使用`~=`精准匹配::(\(point.0),\(point.1))")
    fallthrough
case (5..<10,0...20):
    print("表达式模式使用`~=`范围匹配:(\(point.0),\(point.1))")
default:
    print("未匹配")
}

参考资料:
swift 5.1官方编程指南


了解更多iOS及相关新技术,请关注我们的公众号:

image

小编微信:可加并拉入《QiShare技术交流群》。

image

关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)

推荐文章:
浅谈编译过程
深入理解HTTPS
浅谈 GPU 及 “App渲染流程”
iOS 查看及导出项目运行日志
Flutter Platform Channel 使用与源码分析
开发没切图怎么办?矢量图标(iconFont)上手指南
DarkMode、WKWebView、苹果登录是否必须适配?
奇舞团安卓团队——aTaller
奇舞周刊

上一篇 下一篇

猜你喜欢

热点阅读