转载自泊学(boxueio.com)

这一节,我们结束对IUO的讨论,来看下Swift 4.2中,我们还没有提及的与IUO相关的修改。

对[T]!使用map方法

第一个场景,是对[T]!这种类型使用map方法:

let values: [Any]! = [C()] let transformed = values.map { $0 as! C }

在Swift 4.2之前的版本里,会先对values的值进行unwrap,得到一个[Any],然后再调用[Any]的map方法对数组中元素进行变换,即使你是否对ImplicitlyUnwrappedOptional这个类型扩展了map方法也是如此。

但到了4.2之后,就不是这样了。values的类型已经变成了Optional<[Any]>,values.map调用的会是Optional<T>.map方法,因此这时的$0的类型就变成了[Any]。把[Any]强制转型成C,总是失败的,于是,我们会看到这样一条编译器警告:

其实这里,是有一个类型选择问题的,究竟应该调用Array还是Optional的map方法呢?对于编译器来说,如果可以不进行额外操作就让{ $0 as! C }通过编译,编译器当然应该倾向于直接编译。而恰好,{ $0 as! C }这样的语法对于Optional来说是完全合法的。因此,编译器当然倾向于什么都不做,直接进行编译了。

但显然,这样做从语义上说,和我们理解的有些偏差,毕竟我们实际上是希望对Any进行类型转换的。为此,我们有两种处理方法:

第一种,是利用optional chaining,在values非nil的情况下调用map,此时就会调用Array的map方法了。这样,我们会得到一个Optional<[C]>

let transformed = values?.map { $0 as! C }

第二种,当然就是简单粗暴的直接把optional unwrap出来。这样,我们就会冒着崩溃的风险,直接得到一个[C]:

let transformed = values!.map { $0 as! C }

看到这,你可能会隐隐有一个感觉。是不是我的代码很多地方都存在类似的调用选择问题需要修改啊?实际上,情况没有你预感的这么复杂,绝大多数时候,我们无需关心上面这个变化。例如,对下面这个例子:

let intValues: [Int]! = [1] let transformedInts = intValues.map { $0 + 1 }

这次,为了让{ $0 + 1 }编译通过,编译器别无选择,只能把intValues的值解出来,然后对Int加1才行。因此,尽管intValues的类型是Optional<[Int]>。但是调用map仍旧会导致intValues的值被unwrap。

T! 不再能够作为函数重载的凭据

了解了方法的适配方式时候,我们再来看函数参数类型的适配。在Swift 4.2中,如果你用T!和T?作为重载函数的依据,你会得到一个错误:redeclaratioin of 'fn'。例如:

func fn(_: Int?) {} func fn(_: Int!) {}

实际上,对于这种optional参数,本来,T!和T?类型就是通用的。例如,对于func fn(_: Int?) {}来说,下面两种用法都是合法的:

var intIUO: Int! = 1 var intOptional: Int? = 1 fn(intIUO)
fn(intOptional)

既然如此,当然也就无需再做重载了。

nil值桥接方式的改变

最后一个要介绍的变化,和nil值有关。在Swift 4.2之前,Swift中的nil桥接到OC中,会导致运行时错误,进而导致程序崩溃。在4.2之后,桥接nil会得到NSNull对象。来看下面这个例子:

class A: NSObject {} let iuoElement: A! = nil let array: [Any] = [iuoElement as Any] let nsArray = array as NSArray let element = nsArray[0]

上面的代码,在Swift 4.2之前,nsArray[0]会因为访问了nil导致运行时错误。但在Swift 4.2之后,我们可以这样:

if let value = element as? NSNull { print("Nil value")
} else { print("Non nil value")
}

返回
顶部