转载自泊学(boxueio.com)

我们继续讨论和IUO有关的话题。当IUO不是一个独立的类型之后,所有与之有关的类型自动推导规则也发生了对应的变化。而这些变化,用一句话总结就是:你不能推导出一个不是类型的类型。其实,这和我们上一节说的,不能在别名和数组的定义中使用IUO的含义有些类似。

先来看下面这两个例子:

var x: Int! let y = x // Int? func id<T>(_ value: T) -> T { return value }
type(of: id(x)) // Int? 

这里,尽管x的定义是Int!,但是无论是y的类型,还是id(x)的返回值的类型,都是Optional<Int>,也就是说,type inference推导不出来Int!这样的的类型,因为它不是一个类型。并且,IUO可以自动解值的能力,也不会用在类型推导的过程里,继续看这个例子:

func forcedResult() -> Int! { return x }
type(of: forcedResult) // () -> Optional<Int> func apply<T>(_ fn: () -> T) -> T { return fn() } // Error: Value of optional type 'Int?' not unwrapped let n: Int = apply(forcedResult)

可以看到,尽管forcedResult的定义里返回了Int!,但是Swift推导出来的类型是() -> Int?。正因为如此,我们也无法把apply的返回值赋值给一个Int类型的变量。根据类型自动推导的规则,apply不会返回Int!,而是Int?,自然也就无法自动对这个返回值进行unwrapped了。

应用在AnyObject属性查询时的改变

接下来,我们来看在查询AnyObject属性时的一个变化。为了了解这个过程,我们先定义两个class:

class A: NSObject {} class C { @objc var n: A! = A()
}

然后,定义一个接受AnyObject参数的函数,并调用它:

func getProperty(object: AnyObject) {
    type(of: object.n) if let n = object.n {
        type(of: n)
    }
}

getProperty(object: C())

在4.2之前的Swift版本里,访问AnyObject的属性,得到的都是T!。例如,object.n得到的类型就是A!!。当我们通过if let n = object.nunwrap的时候,实际上只解掉了一层,因此,n的类型实际上是A!,只不过它有自动解值的特性,因此用起来像A罢了。

但到了4.2之后,情况就不是这样了,AnyObject的属性查询,得到的结果都是T?,因此,object.n得到的类型是A??,这样,我们就不能在if let n = object.n的作用域里,直接把n当作A的对象使用了,在这个作用域里,它的类型是A?。为了得到真正的A对象,我们得这样:

func getProperty(object: AnyObject) {
    type(of: object.n) if let n: A = object.n {
        type(of: n)
    }
}

当我们在if let中强制约定了类型A之后,这里就会发生两次unwrap,一次是unwrap AnyObject的查询结果,一次是unwrap查询结果自身,得到对象A。

异常处理相关的改变

和查询AnyObject的属性类似,另一个会带来双重optional的场景是使用try?调用可能发生异常的函数。如果这个函数本身返回optional类型,就会发生这种情形。我们来看个例子:

func fn() throws -> Int! { return 11 }

对于这个函数,当我们使用try? fn()调用的时候,在Swift 4.2之前,得到的返回值的类型是:Optional<ImplicitlyUnwrappedOptional<Int>>。也就是说,下面的代码里,我们可以自动unwrap到Int值:

// This works before Swift 4.2 if let value = try? fn() { // unwrap outer optional let n: Int = value // unwrap innner IUO }

但在Swift 4.2版本之后,try? fn()的结果是Optional<Optional<Int>>。于是,上面的代码就无法通过编译了,内层的optional无法自动unwrap。为了让它恢复编译,我们得这样:

if let value: Int = try? fn() { let n: Int = value
}

这样,在if let语句中,借助type inference,就会完成两次unwrap,我们就可以得到包含在其中的Int了。

switch语句中的类型匹配

我们要介绍的最后一个和类型推导有关的改动,是switch语句中的匹配。来看下面这个例子:

func switchDemo(_ input: String!) -> String { switch input { case let output: return output
    }
}

在Swift 4.2之前,output的类型是String!,因此我们可以直接返回ouput,自动得到其封装的字符串作为返回值。但是,在Swift 4.2之后,output的类型会被推导为String?,因此,上面的代码会报错,提示我们无法自动把一个String?变成String。对此,我们有两种解决方法。

第一种,就是在匹配的结果里,手工unwrap到String:

// In Swift 4.2 and later func switchDemo(_ input: String!) -> String { switch input { case let output: return output!
    }
}

第二种,就是我们明确去匹配optional非nil的情况:

func switchDemo(_ input: String!) -> String { switch input { case let output?: return output default: return "nil" }
}

相比前者,这种方法更为安全。

返回
顶部