讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 4.11 保護類型 >

4.11 保護類型

Swift提供了幾個內建的保護類型,它們可以通過一個聲明表示多種實際類型。

4.11.1  AnyObject

在實際的iOS編程中,最常使用的保護類型是AnyObject。它實際上是個協議;作為協議,其內部完全是空白的,沒有屬性,也沒有方法。它有個很特別的特性,那就是所有的類類型都會自動遵循它。因此,在需要AnyObject的地方,我們可以賦值或傳遞任何類實例,並且可以進行雙向的類型轉換:


class Dog {
}
let d = Dog
let any : AnyObject = d
let 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 NSString
let s2 = any as! String
let i = 1
let any2 : AnyObject = i // implicitly casts to NSNumber
let 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 = Cat
let s = c.noise
  

上述代碼竟然可以編譯通過。此外,代碼運行時也不會崩潰!noise屬性的類型是包裝其原始類型的一個Optional,即包裝String的Optional。如果類型為AnyObject的對象沒有實現noise,那麼結果就是nil,什麼都不會發生。另外,與可選協議屬性不同,本示例中的Optional是隱式展開的。因此,如果AnyObject實際上有noise屬性(比如,它是個Dog),那麼生成的隱式展開String就可以直接當成String了。

下面再來看看方法調用:


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

上述代碼依然可以編譯通過。如果類型為AnyObject的對象沒有實現bark,那麼bark()調用就不會執行;方法結果類型已經被包裝到了Optional中,因此s的類型是個String?,並且已經被設為了nil。如果AnyObject具有bark方法(比如,如果它是個Dog),那麼結果就是一個包裝了返回String的Optional。如果在AnyObject上調用bark!(),那麼結果就是個String,不過如果AnyObject沒有實現bark,那麼應用將會崩潰。與可選協議成員不同,在發送消息時甚至都不用將其展開。如下代碼是合法的:


let c : AnyObject = Cat
let 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.self
let 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 instance
anyExpecter(String)      // a struct
anyExpecter(Dog)       // a class instance
anyExpecter(Dog)         // a class
anyExpecter(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,那麼控制台就不會打印出任何內容。