swift詳解之七------------你真的瞭解構造過程嗎(構造過程和析構過程)

你真的瞭解構造過程嗎(構造過程和析構過程)

注:本文爲作者整理 , 儘量沒有廢話,都是乾貨 。希望看官們可以有所收穫。


1、構造過程

構造過程是爲了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包含了爲實例中的每個屬性設置初始值和爲其執行必要的準備和初始化任務,在類中聲明一個存儲型變量 ,必須爲其賦初始值 。可以通過構造器爲其賦值

當我們創建一個類或者結構體的時候 系統默認給我們創建一個無參數的構造器 ,語法如下 :

init() {
    //可以在這裏給存儲型變量賦初始值
}
class Student{
    var name:String
    init(){
        name = "zhangsan "
    }
}

這裏我們在構造方法中爲name賦了初值 ,當然你也可以直接var name:String=“zhangsan” 定義的時候指定一個初始值
你可以通過輸入參數和可選屬性類型來定製構造過程,也可以在構造過程中修改常量屬性

class Student1{
    var name:String
    let age:Int
     init(name:String){
       self.age = 20
       self.name = name
    }
    init(){
        self.age = 30
        self.name = "zhangsan"
    }
    init(nickName name:String){
        self.age = 40
        self.name = "Nike Name:\(name)"
    }
}

這個類中我們定義了三個構造方法 ,你可以根據需要選擇調用任何一個

let st = Student1()
print(st.name)

let st1 = Student1(name: "lisi")
print(st1.name)

let st2 = Student1(nickName: "wangwu")
print(st2.name)
//結果:
//zhangsan
//lisi
//Nike Name:wangwu

swift 會爲每個構造器自動添加外部參數名 ,相當於唯一符號,也可以自己制定外部參數名 。對外部參數名不瞭解的可以去看Swift詳解之三———-函數(你想知道的都在這裏)
如果你給一個變量聲明位可選類型 , swift會默認賦爲 nil ,證明這是一個在邏輯上允許爲空得存儲型屬性var tele:Int?

在一個結構體中,不僅有默認的無參數構造器 , 系統還有聲明一個逐一成員構造器

struct MySize {
    var width = 0.0,height = 0.0
}
var size = MySize(width: 27.3,height: 45.2)    //系統會自動給結構體創建一個逐一成員構造器。

如果在結構體中 ,自己顯示指定了構造器 ,將不能再調用逐一構造和默認無參數構造

struct MySize1 {
    var width = 0.0,height = 0.0

    init(width:Double){
        self.width = width ;
        height = 2*width ;
    }
//    init(){
//    
//    }
}
//var size1 = MySize1(width: 27.3,height: 45.2)  //報錯 extra argument 'height' in call
//var size1 = MySize()  //invalid redeclaration of 'size1'
var size1 = MySize1(width: 27.3)  //只能通過這種方式

不過可以自己手動再添加那兩個 。

  • 指定構造器和便利構造器

這個算構造器這塊比較繞的地方 ,有涉及到繼承重載的一些東西 。這些都是面向對象的基礎 ,這裏不再詳細說這些 ,做過任何一門面嚮對象語言的應該都回比較瞭解 。後面有時間應該會專門寫一設計模式的模塊 。

類裏面的所有存儲型屬性–包括所有繼承自父類的屬性–都必須在構造過程中設置初始值。

指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。(我們上面例子的構造器 都是指定構造器 )

便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,併爲其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例你應當只在必要的時候爲類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清、晰明。

一下說了這麼多概念 ,看着都頭暈 ,總結下就是 指定構造器可以根據父類鏈往上調用父類的構造器 , 遍歷構造器調用同一個類中的指定構造器。。就這兩句重要的。

我們來看個例子

class Book{
    var name:String
    init(name:String){
        self.name = name
    }
    convenience init(){
        self.init(name:"Swift全解析")
    }
}

我們這裏也提供了一個沒有參數的構造器init(),這個init()構造器爲新實例提供了一個默認的佔位名字”Swift全解析” ,這個就是便利構造器 ,前面用關鍵字 convenience 聲明 。只能調用本類中的指定構造器 。

var swift = Book()
print(swift.name) //Swift全解析
var s  = Book(name:"object-c")
print(s.name) //object-c

這個就不用解釋了。

我們來搞一個子類來提供書本的數量 。

class CountBook:Book{
    var bookCount:Int
    init(name:String,count:Int){
        self.bookCount = count
        super.init(name:name);
    }
    override convenience  init(name:String){
        self.init(name:name,count:1);
    }

}

這裏在指定構造器中傳入兩個參數 ,並且往上調用了父類的構造器 。遍歷構造器 ,重寫了父類的構造器 ,並且調用了本類的指定構造器 。

let cb2 = CountBook(name:"精通 Swift ")
print("\(cb2.bookCount) , \(cb2.name)") //1 , 精通 Swift

//上面是調用CountBook的便利構造器 count給了初始值所以這裏count是1 
let cb1 = CountBook()
print(" \(cb1.bookCount),\(cb1.name)") // 1,Swift全解析
//這裏估計有些人不太理解了,你看父類 ,init() 是不是init(name:String) 的便利構造器 ,然後子類重寫了init(name:String) 又加了count得初始化 。。明白了吧!!

看了這些例子我們再來看下官方給的一些概念性的東西 。

指定構造器和便利構造器
類裏面的所有存儲型屬性–包括所有繼承自父類的屬性–都必須在構造過程中設置初始值。
Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。
指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。

便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,併爲其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例
你應當只在必要的時候爲類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清、晰明。
構造器鏈
爲了簡化指定構造器和便利構造器之間的調用關係,Swift 採用以下三條規則來限制構造器之間的代理調用:
1. 指定構造器必須調用其直接父類的的指定構造器。
2. 便利構造器必須調用同一類中定義的其它構造器。
3. 便利構造器必須最終以調用一個指定構造器結束。
(指定構造器必須總是向上代理
便利構造器必須總是橫向代理)

兩段式構造過程
Swift 中類的構造過程包含兩個階段。第一個階段,每個存儲型屬性通過引入它們的類的構造器來設置初始值。當每一個存儲型屬性值被確定後,第二階段開始,它給每個類一次機會在新實例準備使用之前進一步定製它們的存儲型屬性。

兩段式構造過程的使用讓構造過程更安全,同時在整個類層級結構中給予了每個類完全的靈活性。兩段式構造過程可以防止屬性值在初始化之前被訪問;也可以防止屬性被另外一個構造器意外地賦予不同的值。

Swift 編譯器將執行 4 種有效的安全檢查,以確保兩段式構造過程能順利完成:

安全檢查 1

指定構造器必須保證它所在類引入的所有屬性都必須先初始化完成,之後才能將其它構造任務向上代理給父類中的構造器。

如上所述,一個對象的內存只有在其所有存儲型屬性確定之後才能完全初始化。爲了滿足這一規則,指定構造器必須保證它所在類引入的屬性在它往上代理之前先完成初始化。

安全檢查 2

指定構造器必須先向上代理調用父類構造器,然後再爲繼承的屬性設置新值。如果沒這麼做,指定構造器賦予的新值將被父類中的構造器所覆蓋。

安全檢查 3

便利構造器必須先代理調用同一類中的其它構造器,然後再爲任意屬性賦新值。如果沒這麼做,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋。

安全檢查 4

構造器在第一階段構造完成之前,不能調用任何實例方法、不能讀取任何實例屬性的值,也不能引用self的值。

以下是兩段式構造過程中基於上述安全檢查的構造流程展示:

階段 1

某個指定構造器或便利構造器被調用;
完成新實例內存的分配,但此時內存還沒有被初始化;
指定構造器確保其所在類引入的所有存儲型屬性都已賦初值。存儲型屬性所屬的內存完成初始化;
指定構造器將調用父類的構造器,完成父類屬性的初始化;
這個調用父類構造器的過程沿着構造器鏈一直往上執行,直到到達構造器鏈的最頂部;
當到達了構造器鏈最頂部,且已確保所有實例包含的存儲型屬性都已經賦值,這個實例的內存被認爲已經完全初始化。此時階段1完成。
階段 2

從頂部構造器鏈一直往下,每個構造器鏈中類的指定構造器都有機會進一步定製實例。構造器此時可以訪問self、修改它的屬性並調用實例方法等等。
最終,任意構造器鏈中的便利構造器可以有機會定製實例和使用self。

構造器的繼承和重載
跟 Objective-C 中的子類不同,Swift 中的子類不會默認繼承父類的構造器。Swift 的這種機制可以防止一個父類的簡單構造器被一個更專業的子類繼承,並被錯誤的用來創建子類的實例。

在重載構造器時你必需使用關鍵字override。即使你重載的是默認構造器 也要加上override (重載屬性 方法 下標腳本的時候都要加上override 關鍵字 )

看完這段話,雲裏霧裏的。。


class Person {
    var name = ""
}

class Teacher:Person {

    var age:Int
    override init(){
        age=10           //如果需要對自己的屬性進行初始化  在super之前
        super.init();  //必須先調用父類的構造 對父類屬性進行舒適化
        name = "lisi"  //這裏對父類的屬性重新初始化
    }
}

這個例子包含了初始化自己,調用父類構造器 初始化父類屬性的順序 。是不是很清楚明瞭。

  • 自動構造器的繼承

子類不會默認繼承父類的構造器。但是如果特定條件可以滿足,父類構造器是可以被自動繼承的
1、如果子類沒有定義任何指定構造器,它將自動繼承所有父類的指定構造器。

2、如果子類提供了所有父類指定構造器的實現–不管是通過規則1繼承過來的,還是通過自定義實現的–它將自動繼承所有父類的便利構造器。

這個不用過多解釋了吧,前面的例子都有 很好理解 、

  • 可失敗構造器

如果一個類或者結構體在構造過程中可能會失敗,爲其定義一個可失敗的構造器是非常有必要的,語法 init? 注意:可失敗的參數名和參數類型不能和其他非可失敗的相同 通過return nil 來表示可失敗構造器在什麼情況下失敗

來看一個例子

class Animal{
    var name:String
    init?(name:String) {
        self.name = name
        if (name.isEmpty){
            return nil
        }
    }
}
let sheep = Animal(name: "sheep")
print(sheep?.name) //Optional("sheep")

let dog = Animal(name: "")
print(dog?.name) //nil

當然你也可以在鏈式構造器(便利構造 指定構造等) 中使用可失敗構造器 ,也可以早重載可失敗構造器

  • 必要構造器

在類的構造前面 加required 表明所有該類的子類都必須實現該構造器 。

class Book1{
    var  name : String
    required init(name:String){
        self.name = name
    }
}


class swiftBook:Book1 {

}
//let sw = swiftBook() //missing argument for parameter 'name' in call    不能這樣調用啦,必須加參數
  • 通過閉包來設置默認值

意閉包結尾的大括號後面接了一對空的小括號。這是用來告訴 Swift 需要立刻執行此閉包。如果你忽略了這對括號,相當於是將閉包本身作爲值賦值給了屬性,而不是將閉包的返回值賦值給屬性

如果你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都還沒有初始化。這意味着你不能夠在閉包裏訪問其它的屬性,就算這個屬性有默認值也不允許。同樣,你也不能使用隱式的self屬性,或者調用其它的實例方法。


class Test {

    var dog = "dog"

    var name:String = {
        var n = "zs"
        //在這裏不能使用self.dog
        return n.uppercaseString
    }()

}

let t = Test()
print(t.name)  //ZS

2、析構過程

在一個類的實例被釋放之前,析構函數被立即調用。用關鍵字deinit來標示析構函數,類似於初始化函數用init來標示。析構函數只適用於類類型。
Swift 通過自動引用計數(ARC)處理實例的內存管理。通常當你的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,你可能需要進行一些額外的清理。例如,如果創建了一個自定義的類來打開一個文件,並寫入一些數據,你可能需要在類實例被釋放之前關閉該文件。

在類的定義中,每個類最多只能有一個析構函數。析構函數不帶任何參數,在寫法上不帶括號

deinit {
     //執行析構過程
}

析構函數是在實例釋放發生前一步被自動調用。不允許主動調用自己的析構函數。子類繼承了父類的析構函數,並且在子類析構函數實現的最後,父類的析構函數被自動調用。即使子類沒有提供自己的析構函數,父類的析構函數也總是被調用。

因爲直到實例的析構函數被調用時,實例纔會被釋放,所以析構函數可以訪問所有請求實例的屬性,並且根據那些屬性可以修改它的行爲(比如查找一個需要被關閉的文件的名稱)。

析構這裏就是官方的一些解釋了 ,沒有什麼好解釋的,語法就這種 ,在具體的業務中根據自己的需求具體使用吧。。

學習iOS,有他就夠了,小碼哥視頻,傳智、黑馬、各種swift書籍

發佈了80 篇原創文章 · 獲贊 12 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章