首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》4.11 保护类型

关灯直达底部

Swift提供了几个内建的保护类型,它们可以通过一个声明表示多种实际类型。

4.11.1  AnyObject

在实际的iOS编程中,最常使用的保护类型是AnyObject。它实际上是个协议;作为协议,其内部完全是空白的,没有属性,也没有方法。它有个很特别的特性,那就是所有的类类型都会自动遵循它。因此,在需要AnyObject的地方,我们可以赋值或传递任何类实例,并且可以进行双向的类型转换:


class Dog {}let d = Doglet any : AnyObject = dlet d2 = any as! Dog  

某些非类类型的Swift类型(如String和基本的数字类型)都可以桥接到Objective-C类型上,它们是由Foundation框架定义的类类型。这意味着,在Foundation框架下,Swift桥接类型可以赋值、传递或转换为AnyObject,即便它并非类类型也可以,这是因为在背后,它首先会被自动转换为相应的Objective-C桥接类类型,AnyObject也可以向下类型转换为Swift桥接类型。比如:


let s = /"howdy/"let any : AnyObject = s // implicitly casts to NSStringlet s2 = any as! Stringlet i = 1let any2 : AnyObject = i // implicitly casts to NSNumberlet i2 = any2 as! Int  

我们常常会在与Objective-C交换的过程中遇到AnyObject。Swift可以将任何类类型转换为AnyObject以及将AnyObject转换为类类型的能力类似于Objective-C可以将任何类类型转换为id以及将id转换为类类型的能力。实际上,AnyObject就是Swift版本的id。

比如,你可以通过NSUserDefaults、NSCoding与键值编码(参见第10章)根据字符串的键名来获取到不确定的类对象;这种对象会以AnyObject的形式进入Swift中;特别地,其形式是包装了AnyObject的一个Optional,因为键可能不存在,这样Cocoa要能返回nil。不过,一般来说,你很少会用到AnyObject;你要让Swift知道对象到底是什么类型的。展开Optional并将其从AnyObject向下类型转换是你的职责。如果你知道其类型是什么,那就可以强制展开并通过as!运算符进行强制转换:


required init ( coder decoder: NSCoder ) {    let s = decoder.decodeObjectForKey(Archive.Color) as! String    // ...}  

当然,在使用as!对AnyObject进行向下类型转换时要将其转换为正确的类型,否则当代码运行时程序就会崩溃,转换也是不可能的事情了。如果不确定,那么可以使用is与as?运算符来确保转换是安全的。

1.压制类型检查

AnyObject一个令人惊叹的特性是它可以将编译器对某条消息是否可以发送给某个对象的判断推迟,这类似于Objective-C,在Objective-C中,id类型会导致编译器推迟对什么消息可以发送给它的判断。因此,你可以向AnyObject发送消息,而不必将其转换为真正的类型。(不过,如果知道对象的真实类型,那么你可能还是想将其转换到该类型上。)

你不能随意向AnyObject发送消息;消息必须要对应于满足如下条件之一的类成员:

·它要是Objective-C类的成员。

·它要是你自己定义的Objective-C类的Swift子类(或扩展)的成员。

·它要是Swift类的成员,并且标记为@objc(或dynamic)。

本质上,该特性类似于本章之前介绍的可选协议成员,只不过有一些微小的差别。先从两个类开始:


class Dog {    @objc var noise : String = /"woof/"    @objc func bark -> String {        return /"woof/"    }}class Cat {}  

Dog的属性noise与方法bark都被标记为了@objc,这样它们就可以作为发送给AnyObject的潜在消息了。为了证明这一点,我来创建一个AnyObject类型的Cat,并向其发送一条消息。首先从noise属性开始:


let c : AnyObject = Catlet s = c.noise  

上述代码竟然可以编译通过。此外,代码运行时也不会崩溃!noise属性的类型是包装其原始类型的一个Optional,即包装String的Optional。如果类型为AnyObject的对象没有实现noise,那么结果就是nil,什么都不会发生。另外,与可选协议属性不同,本示例中的Optional是隐式展开的。因此,如果AnyObject实际上有noise属性(比如,它是个Dog),那么生成的隐式展开String就可以直接当成String了。

下面再来看看方法调用:


let c : AnyObject = Catlet s = c.bark?  

上述代码依然可以编译通过。如果类型为AnyObject的对象没有实现bark,那么bark()调用就不会执行;方法结果类型已经被包装到了Optional中,因此s的类型是个String?,并且已经被设为了nil。如果AnyObject具有bark方法(比如,如果它是个Dog),那么结果就是一个包装了返回String的Optional。如果在AnyObject上调用bark!(),那么结果就是个String,不过如果AnyObject没有实现bark,那么应用将会崩溃。与可选协议成员不同,在发送消息时甚至都不用将其展开。如下代码是合法的:


let c : AnyObject = Catlet s = c.bark  

上述代码就好像是强制展开调用一样:结果是个String,不过这么做可能导致应用崩溃。

2.对象恒等性与类型恒等性

有时,你想要知道的并非某个对象是什么类型,而是某个对象本身是否是你所认为的那个特定的对象。值类型不会出现这个问题,但引用类型却会出现,因为可能会有多个不同的引用指向同一个对象。类是引用类型,因此类实例会遇到这个问题。

Swift的解决方案就是恒等运算符(===)。该运算符可用于使用了AnyObject协议的对象类型实例,就像类一样!它会比较对象引用。这并不是比较两个值是否相等,就像相等运算符(==)那样;你所做的是判断两个对象引用是否指向了相同的对象。恒等运算符还有一个否定版本(!==)。

一个典型的使用场景是类实例来自于Cocoa,你需要知道它是否是已经拥有的某个引用所指向的特定对象。比如,NSNotification有一个object属性,它用于标识通知(通常情况下,它是通知最初的发送者);Cocoa对其底层类型一无所知,因此你会接收到一个包装了AnyObject的Optional。就像==一样,===运算符可与Optional无缝衔接,因此你可以使用它来确保通知的object属性就是你所期望的对象:


func changed(n:NSNotification) {    let player = MPMusicPlayerController.applicationMusicPlayer    if n.object === player {        // ...    }}  

4.11.2  AnyClass

AnyClass是AnyObject对应的类,它对应于Objective-C的Class类型。通常在Cocoa API的声明中如果需要一个类,那这正是AnyClass的用武之地。

比如,UIView的layerClass类方法对应的Swift声明如下代码所示:


class func layerClass -> AnyClass  

上述代码表示:如果重写了该方法,那么需要让其返回一个类。这可能是一个CALayer子类。要想在自己的实现中返回一个实际的类,请向类名发送self消息:


override class func layerClass -> AnyClass {    return CATiledLayer.self}  

对AnyClass对象的引用与对AnyObject对象的引用行为相似。你可以向其发送Swift知道的任何Objective-C消息,即任何Objective-C类消息。为了说明问题,我们从两个类开始:


class Dog {    @objc static var whatADogSays : String = /"woof/"}class Cat {}  

Objective-C会看到whatADogSays并将其作为一个类属性。因此,你可以将whatADogSays发送给AnyClass引用:


let c : AnyClass = Cat.selflet s = c.whatADogSays  

对类的引用(可以通过向实例引用发送dynamicType获得,也可以通过向类型名发送self获得)类型使用了AnyClass,你可以通过===运算符比较这种类型的引用。实际上,这种方式可以判断指向类的两个引用是否指向了相同的类。比如:


func typeTester(d:Dog, _ whattype:Dog.Type) {    if d.dynamicType === whattype {        // ...    }}  

只有当d与whattype是相同类型时条件才为true(不考虑多态);比如,如果Dog有个子类NoisyDog,那么如果参数为Dog()与Dog.self或NoisyDog与NoisyDog.self,条件就为true;但如果参数为NoisyDog()与Dog.self,那么条件就为false。尽管没有使用多态,但这么做是有意义的,因为当右侧是类型引用时,你没法使用is运算符,必须要是字面类型名才行。

4.11.3  Any

Any类型是被所有类型自动使用的一个空协议的类型别名。这样,在需要一个Any对象时,你可以传递任何对象:


func anyExpecter(a:Any) {}anyExpecter(/"howdy/")     // a struct instanceanyExpecter(String)      // a structanyExpecter(Dog)       // a class instanceanyExpecter(Dog)         // a classanyExpecter(anyExpecter) // a function  

类型为Any的对象可以与任何对象或函数类型进行比较,也可以向下类型转换为它们。为了说明这一点,下面是一个带有关联类型的协议,并且有两个使用者显式地进行解析:


protocol Flier {    typealias Other}struct Bird : Flier {    typealias Other = Insect}struct Insect : Flier {    typealias Other = Bird}  

现在有一个函数接收一个Flier参数和一个类型为Any的参数,并测试第2个参数的类型是否与Flier解析出的Other类型一样;这个测试是合法的,因为Any可以与任何类型进行比较:


func flockTwoTogether<T:Flier>(flier:T, _ other:Any) {    if other is T.Other {        print(/"they can flock together/")    }}  

如果使用Bird与Insect调用flockTwoTogether,那么控制台会打印出“they can flock together”。如果使用Bird与其他类型的对象调用flockTwoTogether,那么控制台就不会打印出任何内容。