標籤:swift 構造器
Swift 構造器探究什麼時候要用構造器?對於類(Class)
其實在其他語言中,比如說Java對於屬性的初始化沒有嚴格的要求。甚至在Model層只有對應屬性的get,set訪問器。而在Swift中無論是對於結構體(Structure)還是類(Class),如果其中存在儲存屬性(stored property),那麼必須在合適的地方給它賦初始值,也就是初始化。不能讓它們成為不確定的狀態,即沒有初始化。關於初始化,Swift提供了兩種方式。一種是屬性定義的時候初始化,也就是賦預設值。 第二種是在構造器中初始化。這裡做個小結:儲存屬性必須初始化,初始化的方式且只能在以上兩種方式選擇,並且至少包含其中一種。
而對於第一種,在Swift中又有兩種初始化方式。第一種,是給予明確的值。比如說var name = "Bob"
Swift的資料類型推斷機制會自動推斷出name
是一個String類型變數,初始值為Bob
。第二種,是Optional類型初始化,通常用在這個屬性在程式運行過程中可能存在值也可能不存在值的時候。比如說var avatar: UIImage?
一個人可能沒有頭像。假設avatar
是Person
類的一個儲存屬性,程式在運行過程,如果Person
類建立一個執行個體let myPerson = Person()
, 如果沒有給myPerson.avatar
賦值,那麼myPerson
中avatar
屬性自動初始化為nil
。如果myPerson.avatar = UIImage(named: "prettyGirl")
,那麼avatar屬性初始化就是UIImage(named: "prettyGirl")
。
而對於第二種初始化方式,更加具體的說,是如果沒有是實現第一種初始化方式的時候,必須實現的。也就是說如果你定義一個屬性var avatar: UIImage
既沒有給予明確的初始值,也沒有讓它成為Optional類型,那麼必須實現在構造器中的初始化。這就是什麼時候要用構造器的重點了。
然而Swift的構造器又有兩種,一種是designated構造器,一種是convenience構造器。所有designated構造器都必須初始化那些沒有滿足第一種情況的儲存屬性。注意這裡是類中所有的designated構造器都必須要做這件事。具體怎麼做請看後文Designated構造器
對於結構體(Structure)
Swift中結構體和類的構造器其實差不多。除了結構體中沒有析構器(Deinitializer),不能夠繼承(inherit)以及結構體有memberwise構造器外大體上是一致的。所以你有時候看到一個結構體struct Point
有兩個儲存屬性var x: Double
, var y: Double
,卻沒有任何構造器,但是他們既不符合類中討論的第一種初始化方法(即賦預設值)。那麼它們違背了文法規則嗎?其實不是的,如果結構體沒有自訂的構造器,Swift隱式建立了一個init(x:y:)
的構造器其內容就相當於self.x = x, self.y = y
。如果你手動給結構體加個空內容的構造器init(){ }
,編譯器就報Return from initializer without initializing all stored properties
的錯誤。也就是說,如果你建立了你自己定義的構造器,Swift就默默地幫你把memberwise構造器去掉了,而你自己定義的構造器又沒有對儲存屬性初始化,那麼這違背了文法規則。但是如果你想同時擁有這兩個構造器(memberwise構造器和自訂構造器),你可以把自訂的構造寫到Extension Point{ // custom initializer }
裡面進去。
構造器的繼承designated構造器
designated構造器在Swift中很常見,顧名思義這個構造器就是你類中所有構造器的“原型”。在這個構造器中只調用父類的designated構造器或者不調用其他任何構造器稱為designated構造器。每個類都必須至少有一個designated構造器,但是你會看到有些情況看不見類中聲明designated構造器,那是因為它是一個子類,如果不寫任何designated構造器,將會自動繼承父類所有designated構造器。我們將在下面的自動構造器繼承
中詳細講到。
init(parameters){ // statments }
convenience構造器
convenience構造器是第二種構造器。它主要是橫向代理,就是說在convenience構造器中一定存在也只能存在該類的一個構造器通常用self.init(parameters)
調用該類的一個構造器。當然convenience構造器不是必要的。
convenience init(parameters){ // 調用該類中的一個構造器 self.init(parameters) // customize properties }
類的構造器代理規則
- 規則1:子類中的designated構造器必須調用最近一級父類的desigated構造器
- 規則2:convenience構造器必須在同一個類中調用其他一個構造器
- 規則3:convenience構造器通過調用鏈(代理鏈)調用一個designated構造器
總結下也就說
- desingated構造器必須一直向上代理(即調用最近一級父類的designated構造器)
- convenience構造器必須橫向代理,且代理終點為一個desingated構造器
下面這幅圖(引用自蘋果官方文檔原圖)就表明了這兩點
SubClass的convenience構造器調用了第二個designated構造器(符合規則2),第二個designated的調用了SuperClass的designated構造器(符合規則1),這表明了convenience構造器最終調用的是designatedg構造器(符合規則3)。同理其他的構造器調用亦是如此
下面再來一幅圖(引用自蘋果官方文檔原圖)
Two-Phase初始化
第一階段類中每個儲存屬性必須有初始值,一旦每個儲值屬性的初始狀態被確定了,第二階段就開始了。第二階段就是在新的執行個體可用之前對初始值的修改階段。利用兩階段初始化可以讓初始化安全,防止屬性值在初始化完成之前被訪問,以及屬性值被另外的構造器設定為不同的值。這和OC差不多,唯一區別就是OC在第一階段初始的預設值只能是0或者是nil
初始化有安全檢查機制
- 安全檢查1:一個designated構造器在向上代理之前必須初始化該類中定義的所有儲存屬性
- 安全檢查2:一個designated構造器必須先向上代理調用一個父類的構造器,在修改父類的屬性值之前。如果不這樣,那麼你修改的屬性值,會被父類的構造器初始化屬性的時候覆蓋掉。
- 安全檢查3:一個convenience構造器在對本類任何屬性操作之前(包括父類的屬性以及本類定義的屬性)必須調用另外一個本類中的構造器。如果不是這樣,那麼修改完的屬性很可能就被本類中的構造器初始化屬性的時候覆蓋了。
- 安全檢查4:一個構造器不能調用執行個體方法,讀取任何執行個體屬性的值,或者用作為一個值指向
self
知道第一階段初始化結束
初始化兩個階段
第一階段:
- 一個designated或者convenience構造器在類中被調用
- 類的新執行個體向系統申請記憶體空間,但是記憶體還沒有被初始化
- 該類的一個designated構造器確認所有在該類中定義的儲存屬性已經被初始化。這些屬性的記憶體被初始化。
- designated的構造器告知父類構造器對父類自己的屬性執行相同的操作
- 一直代理到類繼承鏈的最進階
- 一旦到達類繼承鏈的最進階,並且鏈上的最終類確認了它所有的儲存屬性已經初始化完畢,則執行個體記憶體被完全初始化,然後第一階段到此就完成了。
下面這幅圖是第一階段
第二階段:
- 從繼承鏈的最頂端開始每個designated的構造器可以修改執行個體中的屬性值,此時構造器也可以訪問self指標了,可以調用執行個體方法等等。
- 最後在繼承鏈上的任何convenience構造器可以用self指標來修改執行個體
下面這幅圖是第二階段
Swift 構造器探究