實例引用自己的類型是很有用的,比如,向該類型發送消息。在之前的示例中,Dog實例方法通過顯式向Dog類型發送一條消息來獲取到一個Dog類屬性,這是通過使用Dog這個單詞做到的:
class Dog { class var whatDogsSay : String { return \"Woof\" } func bark { print(Dog.whatDogsSay) } }
表達式Dog.whatDogsSay看起來很笨拙並且不靈活。為什麼要在Dog類中使用硬編碼的類名呢?它有一個類;它應該知道是什麼。
在Objective-C中,我們習慣使用類實例方法來處理這種情況。在Swift中,實例可能不是類(可能是結構體實例或枚舉實例);Swift實例擁有類型。Swift針對這一目的提供了一個名為dynamicType的實例方法。實例可以通過該方法訪問其類型。因此,如果不想顯式使用Dog來通過Dog實例調用Dog類方法,那麼下面就是另一種解決方案:
class Dog { class var whatDogsSay : String { return \"Woof\" } func bark { print(self.dynamicType.whatDogsSay) } }
使用dynamicType而非硬編碼類名的重要之處在於它遵循了多態:
class Dog { class var whatDogsSay : String { return \"Woof\" } func bark { print(self.dynamicType.whatDogsSay) } } class NoisyDog : Dog { override class var whatDogsSay : String { return \"Woof woof woof\" } }
下面來看一下結果:
let nd = NoisyDog nd.bark // Woof woof woof
如果調用NoisyDog的bark方法,那麼會打印出\"Woof woof woof\"。原因在於dynamicType的含義是「該實例現在的實際類型,這正是該類型成為動態的原因所在」。我們向NoisyDog實例發送了「bark」消息。bark實現引用了self.dynamicType;self表示當前實例,即NoisyDog,因此self.dynamicType是NoisyDog類,它是獲取到的NoisyDog的whatDogsSay。
還可以通過dynamicType獲取到對像類型的名字(字符串),這通常用於調試的目的。在調用print(myObject.dynamicType)時,控制台上會打印出類型名。
在某些情況下,你會將對像類型作為值進行傳遞。這麼做是合法的;對像類型本身也是對象。下面是你需要知道的幾點:
·聲明接收某個對象類型(如作為變量或參數的類型),請使用點符號加上類型名與關鍵字Type。
·將對像類型作為值(比如,為變量指定類型或將類型傳遞給函數),請使用類型引用(類型名,或實例的dynamicType),後面可以通過點符號跟著關鍵字self。
比如,下面這個函數的參數會接收一個Dog類型:
func typeExpecter(whattype:Dog.Type) { }
如下代碼調用了該函數:
typeExpecter(Dog) // or: typeExpecter(Dog.self)
還可以像下面這樣調用:
let d = Dog // or: let d = NoisyDog typeExpecter(d.dynamicType) // or: typeExpecter(d.dynamicType.self)
為何要這麼做呢?典型場景就是函數是個實例工廠:給定一個類型,它會創建出該類型的實例,可能還會做一些處理,然後將其返回。你可以使用指向類型的變量引用來創建該類型的實例,方式是向其發送一條init(...)消息。
比如,如下Dog類帶有一個init(name:)初始化器,同時它還有一個子類NoisyDog:
class Dog { var name : String init(name:String) { self.name = name } } class NoisyDog : Dog { }
下面是創建Dog或NoisyDog的工廠方法(通過參數來指定),為實例指定一個名字,然後將實例返回:
func dogMakerAndNamer(whattype:Dog.Type) -> Dog { let d = whattype.init(name:\"Fido\") // compile error return d }
如你所見,由於whattype引用了一個類型,所以可以調用其初始化器創建該類型的實例,不過有一個問題,上述代碼無法編譯通過。原因在於編譯器不能確定init(name:)初始化器是否會被Dog的每個子類型所實現。為了消除編譯器的這個疑慮,我們需要通過required關鍵字聲明該初始化器:
class Dog { var name : String required init(name:String) { self.name = name } } class NoisyDog : Dog { }
現在來解釋一下需要將初始化器聲明為required的原因:required會消除編譯器的疑慮;Dog的每個子類都要繼承或重新實現init(name:);因此,可以向指向Dog或其子類的類型引用發送init(name:)消息。現在代碼可以編譯通過,我們也可以調用函數:
let d = dogMakerAndNamer(Dog) // d is a Dog named Fido let d2 = dogMakerAndNamer(NoisyDog) // d2 is a NoisyDog named Fido
在類方法中,self表示類,這正是多態發揮作用之處。這意味著在類方法中,你可以向self發送消息,以多態的形式調用初始化器。下面是一個示例,我們將實例工廠方法移到Dog中,作為一個類方法,並將這個類方法命名為makeAndName。我們需要這個類方法創建並返回一個具名Dog,返回的Dog類型取決於向哪個類發送makeAndName消息。如果調用Dog.makeAndName(),那麼返回的是Dog;如果調用NoisyDog.makeAndName(),那麼返回的是NoisyDog。類型是多態的self類,因此makeAndName類方法可以初始化self:
class Dog { var name : String required init(name:String) { self.name = name } class func makeAndName -> Dog { let d = self.init(name:\"Fido\") return d } } class NoisyDog : Dog { }
其工作方式與我們期望的一致:
let d = Dog.makeAndName // d is a Dog named Fido let d2 = NoisyDog.makeAndName // d2 is a NoisyDog named Fido
不過有一個問題。雖然d2實際上是個NoisyDog,但其類型卻是Dog。這是因為makeAndName類方法在聲明時返回的類型是Dog,這並不是我們想要的結果。我們希望該方法所返回的實例類型與接收makeAndName消息的類型保持一致。換句話說,我們需要一種多態化的類型聲明!這個類型是Self(注意,首字母是大寫的)。它用作方法聲明的返回類型,表示「運行時該類型的實例」。如下:
class Dog { var name : String required init(name:String) { self.name = name } class func makeAndName -> Self { let d = self.init(name:\"Fido\") return d } } class NoisyDog : Dog { }
現在,當調用NoisyDog.makeAndName()時,我們將會得到類型為NoisyDog的NoisyDog。
Self也可以用於實例方法聲明。因此,我們可以編寫出該工廠方法的實例方法版本。下面是Dog類與NoisyDog類的聲明,同時在Dog類中聲明了一個返回Self的havePuppy方法:
class Dog { var name : String required init(name:String) { self.name = name } func havePuppy(name name:String) -> Self { return self.dynamicType.init(name:name) } } class NoisyDog : Dog { }
下面是測試代碼:
let d = Dog(name:\"Fido\") let d2 = d.havePuppy(name:\"Fido Junior\") let nd = NoisyDog(name:\"Rover\") let nd2 = nd.havePuppy(name:\"Rover Junior\")
如你所想,d2是個Dog,不過nd2卻是個類型為NoisyDog的NoisyDog。
講了這麼多可能有些亂,下面來總結一下:
.dynamicType
用在代碼中,發送給實例:表示該實例的多態化(內部)類型,無論實例引用的類型是什麼均如此。靜態/類成員可以通過實例的dynamicType進行訪問。
.Type
用在聲明中,發送給類型:多態類型而不是類型的實例。比如,在函數聲明中,Dog表示需要一個Dog實例(或其子類的實例),而Dog.Type則表示需要Dog類型本身(或是其子類的類型)。
.self
用在代碼中,發送給類型。比如,在需要Dog.Type時,你可以傳遞Dog.self(向實例發送.self是合法的,但卻毫無意義)。
self
用在實例代碼中表示多態語義下的當前實例。
用在靜態/類代碼中表示多態語義下的類型;self.init(...)會實例化該類型。
Self
在方法聲明中,如果指定了返回類型,那麼它表示多態化的類或實例的類。