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

4.7 類型引用

實例引用自己的類型是很有用的,比如,向該類型發送消息。在之前的示例中,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

在方法聲明中,如果指定了返回類型,那麼它表示多態化的類或實例的類。