Swift基礎9

下標腳本

下標腳本 可以定義在類、結構體和枚舉這些目標中,可以認為是訪問集合(collection),列表(list)或序列(sequence)的快捷方式,使用下標腳本的索引設置和獲取值,不需要在調用實例的特定的賦值和訪問方法。

對于同一個目標可以定義多個下標腳本,通過索引值類型的不同來進行重載,下標腳本不限于單個緯度,我們可以定義多個入參的下標腳本滿足自定義類型的需求。

下標腳本語法

下標腳本允許我們通過在實例后面的方括號中傳入一個或者多個索引值來對實例進行訪問和賦值。語法類似于實例方法和計算型屬性的混合。與定義實例方法類似,定義下標腳本使用subscript關鍵字,顯式聲明入參(一個或多個)和返回類型。與實例方法不同的是下標腳本可以設定為讀寫和只讀。這種方式又有點像計算型屬性的getter和setter:

subscript(index: Int) -> Int {
    get {
        //返回入參匹配的Int類型的值
    }
    
    set(newValue){
        //執行賦值操作
    }

}

其中newValue的類型必須和下標腳本定義的返回類型相同。與計算型屬性相同的是set的入參聲明newValue就算不寫,在set代碼塊中依然可以使用默認的newValue這個變量來訪問新賦的值。

與只讀計算型屬性一樣,可以直接將原本應該寫在get代碼塊中的代碼寫在subscript中:

subscript(index:Int) -> {
    //返回與入參匹配的Int類型的值
    return ......
}

eg:

struct TimesTable {
    var time : Int
    
    subscript(index:Int) ->Int {
        
        return index * time
    }
    
}

let theTime = TimesTable(time: 20)
print(theTime[20])

下標腳本用法

根據使用場景不同下標腳本也具有不同的含義。通常下標腳本是用來訪問集合(collection),列表(list)或序列(sequence)中元素的快捷方式。我們可以在我們自己特定的類或結構體中自由的實現下標腳本來提供合適的功能。

下標腳本選項

下標腳本允許任意數量的入參索引,并且每一個入參類型也沒有限制。下標腳本的返回值也可以是任何類型。下標腳本可以使用參數和可變參數,但使用寫入讀出參數或給參數設置默認值都是不允許的。

一個類或結構體可以根據自身需要提供多個下標腳本實現,在定義下標腳本時通過入參的類型進行區分,使用下標腳本時會自動匹配合適的下標腳本進行運行,這就是下標腳本的重載。

struct Test  {
    var x = 0, y = 0, z = 0
    
    
    subscript(x:Int) ->Int {
        
        get{
            switch x {
            case 0:
                return self.x
            case 1:
                return self.y
            case 2:
                return self.z
                
            default:
                return 0
            }
        }
        
        set(newValue){
            switch x {
            case 0:
                self.x = newValue
            case 1:
                self.y = newValue
            case 2:
                self.z = newValue
                
            default:
                print("error")
            }
        }
        
    }
    
    subscript(x:String) -> Int{
        
        get{
            switch x {
            case "0":
                return self.x
            case "1":
                return self.y
            case "2":
                return self.z
                
            default:
                return 0
            }
        }
        
        set(newValue){
            switch x {
            case "0":
                self.x = newValue
            case "1":
                self.y = newValue
            case "2":
                self.z = newValue
                
            default:
                print("error")
            }
        }
        
    }
    
    subscript(x:Int ,y:Int )-> Int{
        return 10000
    }
    
}


var instance = Test(x: 10, y: 20, z: 30)
instance[0] = 100
print(instance)

instance["0"] = 200
print(instance)


instance[1,2]

繼承

一個類可以繼承另一個類的方法,屬性和其他特性。當一個類繼承其他類時,繼承類叫子類,被繼承類叫超類。在Swift中,繼承是區分 類 與其他類型的一個基本特征。

在Swift中,類可以調用和訪問超類的方法,屬性和下標腳本,并且可以重寫這些方法,屬性和下標腳本來優化或修改它們的行為。Swift會檢查你的重寫定義在超類中是否有匹配的定義,以此確保你的重寫行為是正確的。

可以為類中繼承來的屬性添加屬性觀察器,這樣一來,當屬性值改變時,類就會被通知到。可以為任何屬性添加屬性觀察器,無論它原本被定義為存儲型屬性還是計算型屬性。

定義一個基類

不繼承于其它類的類,稱之為基類。

note: Swift中的類并不是從一個通用的基類繼承而來。如果我們不為我們定義的類指定一個超類的話,這個類就自動成為基類。

class Vehicle {
    
    var currentSpeed = 0.0
    
    var description: String {
        return "traveling at \(currentSpeed) mils per hour"
    }
    
    func makeNoise(){
        //什么也不做因為車輛不一定都會有噪音
    }
    
    
}

子類生成

子類生成 指的是在一個已有類的基礎上創建一個新的子類。子類繼承超類的特征,并且可以優化或改變它。我們還可以為子類添加新的特征。

為了指明某個類的超類,將超類名寫在子類名的后面,用冒號分隔:

class someClass : someSuperClass {
//  類定義
}
class Bicycle : Vehicle {
     var hasBaseket = false
    
}

重寫

子類可以為繼承而來的實例方法,類方法,實例屬性或下標腳本提供自己定制的實現。我們把這種行為叫重寫。

如果要重寫某個特性,我們需要在重寫定義的前面加上override關鍵字。這么做,我們就可以明確我們是想提供一個重寫版本,而非錯誤地提供了一個相同的定義。意外的重寫行為可能會導致不可預知的錯誤,任何缺少override關鍵字的重寫都會在編譯時被診斷為錯誤。

override關鍵字會提醒Swift編譯器去檢查該類的超類是否有匹配重寫版本的聲明。這個檢查可以確保你的重寫定義是正確的。

訪問超類的方法,屬性以及下標腳本

當我們在子類中重寫超類的方法,屬性或下標腳本時,有時在你的重寫版本中使用已經存在的超類實現會大有裨益。比如,我們可以優化已有實現的行為,或在一個繼承來的變量中存儲一個修改過的值。

在合適的地方,我們可以使用super前綴來訪問超類版本的方法、屬性或下標腳本:
*.在方法someMethod的重寫實現中,可以通過super.someMethod()來調用超類版本的someMethod方法。
*.在屬性someProperty的getter或setter的重寫實現中,可以通過super.someProperty來訪問超極版本的someProperty的屬性。
*.在下標腳本的重寫實現中,可以通過super[xxx]來訪問超類版本中的相同下標腳本。

重寫方法

在子類中,我們可以重寫繼承來的實例方法或類方法,提供一個定制或替代的方法實現。

class Train: Vehicle {
    
    override func makeNoise() {
        print("uuuuuuuuuu")
    }
    
}

重寫屬性

我們可以重寫繼承來的實例屬性或類屬性,提供自己定制的getter和setter,或添加屬性觀察器使重寫的屬性可以觀察屬性值什么時候發生改變。

1.重寫屬性的getters和setters
我們可以提供定制的getter或setter來重寫任意繼承來的屬性,無論繼承來的屬性是存儲型的還是計算型的屬性。子類并不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型。我們重寫一個屬性時,必需將它的名字和類型都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的。

2.我們可以將一個繼承來的只讀屬性重寫為一個讀寫屬性,只需要我們在重寫版本的屬性里提供getter和setter即可。但是,我們不可以將一個繼承來的讀寫屬性重寫為一個只讀屬性。

note:如果我們在重寫屬性中提供了setter,那么我們也一定要提供getter。如果我們不想在重寫版本的getter里修改繼承來的屬性值,我們就可以直接通過super.someProperty來返回繼承來的值,其中someProperty是我們要重寫的屬性名字。

class Car : Vehicle {
    
    var gear = 1
    
    override var description: String {
        
        return super.description + " in gear \(gear)"
        
    }
    
}

let car = Car()
car.currentSpeed =  100
car.gear = 3
print("\(car.description)")

3.重寫屬性觀察器
我們可以在屬性重寫中為一個繼承來的屬性添加屬性觀察器。這樣一來,當繼承來的屬性值發生改變時,我們就會被通知到,無論那個屬性原本是如何實現的。

note:我們不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。這些屬性的值是不可以被設置的,所以,為它們提供觀察器是不恰當的。此外還要注意,我們不可以同時提供重寫的setter和重寫的屬性觀察器。如果我們想觀察屬性值的變化,并且我們已經為那個屬性提供了定制的setter,那么我們在setter中就可以觀察到任何值的變化了。

class AutoCar : Car {
    
    
    override var currentSpeed: Double {
        
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
    
    
}

防止重寫

我們可以通過把方法,屬性或下標腳本標記為final來防止它們被重寫,只需要在聲明關鍵字前加上final特性即可。(eg: final var , final func ,final class func 以及final subscript)

如果我們重寫了final方法,屬性或下標腳本,在編譯時會報錯。在類擴展中的方法,屬性或下標腳本也可以在擴展定義里記為final。

我們可以通過在關鍵字class前添加 final 特性 來將整個類標記為final的,這樣的類時不可被繼承的,任何子類試圖繼承此類時,在編譯時會報錯。

構造過程

構造過程是使用類、結構體或枚舉類型一個實例的準備過程。在新實例可用前必需執行這個過程,具體操作包括設置實例中每個存儲屬性的初始值和執行其他必須的設置或初始化工作。

通過定義構造器來實現構造過程,這些構造器可以看做是用來創建特定類型新實例的特殊方法。與objective-c中的構造器不同,Swift的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完全正確的初始化。

類的實例也可以通過定義 析構器 在實例釋放之前執行特定的清除工作。

存儲屬性的初始賦值

類和結構體在創建實例時,必須為所有的存儲屬性設置合適的初始值。存儲型屬性的值不能處于一個未知的狀態。
我們可以在構造器中為存儲型屬性賦初值,也可以在定義屬性時為其設置默認值。
note:當我們為存儲型屬性設置默認值或者在構造器中為其賦值時,它們時被直接復制的,不會觸發任何屬性觀察器。

構造器

構造器在創建某特定類型的新實例時調用。它的最簡形式類似于一個不帶任何參數的實例方法,以關鍵字init命名。

init(){
//在此處執行構造過程
}

下面例子中定義了一個用來保存華氏溫度的結構體 Fahrenheit ,它擁有一個Double類型的存儲類型temperature:

struct Fahrenheit {
    
    var temperature : Double
    
    init() {
        temperature = 32.0
    }
    
}

var f = Fahrenheit()
print("the default temperature is \(f.temperature)")

這個結構體定義了一個不帶參數的構造器init,并在里面將存儲屬性temperature的值初始化為32.0

默認屬性值

如前所述,我們可以在構造器中為存儲型屬性設置初始化值。同樣,我們也可以在屬性聲明時為其設置默認值。
note:如果一個屬性總是使用相同的初始值,哪么為其設置一個默認值比每次都在構造器中賦值要好。兩種方法的效果是一樣的,只不過是用默認值讓屬性的初始化和聲明結合的更緊密。使用默認值能讓我們的構造器更簡潔、更清晰,且能通過默認值自動推導出屬性的類型;同時,它也能讓你充分利用默認構造器、構造器繼承等特性。

struct Fahrenheit {
    
    var temperature =  32.0

}

自定義構造過程

我們可以通過輸入參數和可選屬性類型來自定義構造過程,也可以在構造過程中修改常量屬性。

1.構造參數:
自定義 構造器 時,可以在定義中提供構造器參數,指定所需值的類型和名字。構造參數的功能和語法跟函數和方法的參數相同。


struct Test {
    
    var tmpValue : Double
    var tmpValue2: Double
    
    init(tmpValue value:Double){
        tmpValue = value
        tmpValue2 = 0.0
    }
    
    init(tmpValue2 value:Double){
        tmpValue2 = value
        tmpValue = 0.0
    }
    
}

let test1 = Test(tmpValue: 100)
let test2 = Test(tmpValue2: 200)

2.參數的內部名稱和外部名稱
跟函數和方法參數相同,構造參數也存在一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。

然而,狗仔器并不像函數和方法那樣在括號前有一個可辨別的名字。所以在調用構造器時,主要通過構造器中參數名和類型來確定需要調用的構造器。正因為參數如此重要,如果我們在定義構造器時沒有提供參數的外部名字,swift會為每一個構造器的參數自動生成一個跟內部名字相同的外部名,就相當于在每一個構造參數之前加了一個哈希符號。


struct Color {
    
    let red,green,blue:Double
    
    init(red:Double,green:Double,blue:Double){
        
        self.red = red
        self.green = green
        self.blue = blue
    }
    
    init(white:Double){
        
        red = white
        green = white
        blue = white
    }
    
    
}

let color1 = Color(red: 1.0, green: 0.0, blue: 1.0)
let color2 = Color(white: 0.5)

3.不帶外部名的構造器參數
如果你不希望為構造器的某個參數提供外部名字,我們可以使用下劃線(_)來顯示描述它的外部名,以此重寫上面所說的默認行為。

struct Color {
    
    let red,green,blue:Double
    
    init(red:Double,green:Double,blue:Double){
        
        self.red = red
        self.green = green
        self.blue = blue
    }
    
    init(white:Double){
        
        red = white
        green = white
        blue = white
    }
    
    init(_ color : Color){
        self = color
    }
    
}

let color1 = Color(red: 1.0, green: 0.0, blue: 1.0)
let color2 = Color(white: 0.5)
let color3 = Color(color1)

4.可選屬性類型
如果我們定制的類型包含一個邏輯上允許取值為空的存儲型屬性---不管時因為它無法在初始化時賦值,還說因為它可以在之后某個時間點可以賦值為空---我們都需要將它定義為可選類型optional type。可選類型的屬性將自動初始化為空nil,表示這個屬性時故意在初始化時設置為空的。

class Question {
    
    var text: String
    
    var response:String?
    
    init(text:String){
        
        self.text = text
    }
    
    func ask(){
        print(self.text)
    }
    
}

let question = Question(text: "Do you like China")
question.ask()
question.response = "Yes , i like"

調查問題在問題提出之后,我們才能得到答案。所以我們將屬性回答response聲明為String?類型。或者說是可選字符串類型optional String。當Question實例化時,它將自動賦值為空nil,表明暫時還不存在此字符串。

5.構造過程中常量屬性的修改
我們可以在構造工程中的任意時間點修改常量屬性的值,只要在構造過程結束時是一個確定的值。一旦常量屬性被賦值,它將永遠不可更改。

note:對于類的實例來說,它的常量屬性只能在定義它的類的構造過程中修改:不能在子類中修改。

class Question {
    
    let text: String
    
    var response:String?
    
    init(text:String){
        
        self.text = text
    }
    
    func ask(){
        print(self.text)
    }
    
}

let question = Question(text: "Do you like China")
question.ask()
question.response = "Yes , i like"

默認構造器

如果結構體和類的所有屬性都有默認值,同時沒有自定義的構造器,那么Swift會給這些構造器和類創建一個默認構造器。這個默認構造器將簡單的創建一個所有屬性值都設置為默認值的實例。

class ShoppingListItem {
    var name: String?
    var num = 1
    var price = 10.0
}

var item = ShoppingListItem()

結構體的逐一成員構造器

除了上面提到的默認構造器,如果構造器對所有存儲型屬性提供了默認值且自身沒有提供定制的構造器,它們能自動獲得一個逐一成員構造器。

逐一成員構造器是用來初始化結構體新實例成員屬性的快捷方式。我們在調用逐一成員構造器時,通過與屬性名相同的參數名進行傳值類完成對成員屬性的初始化賦值。

struct Size {
    var width = 0.0,height = 0.0
}

let theSize = Size(width: 2.5, height: 3.5)

值類型的構造器代理

構造器可以通過調用其它構造器來完成實例的部分構造工程。這一過程稱為構造器代理,它能減少多個構造器間的代碼重復。

構造器代理的實現規則和形式在值類型和類類型中有所不同,值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因為它們只能代理給本身提供的其他構造器。類則不同,他可以繼承自其他類,這意味著類有責任保證其所有繼承的存儲屬性在構造時也能正確的初始化。

對于值類型,我們可以使用self.init在自定義的構造器中引用其他的屬于相同值類型的構造器。并且我們只能在構造器內部調用self.init。

如果我們為某個值類型定義了一個定制的構造器,哪么我們將無法訪問到默認的構造器(如果是結構體,則無法訪問逐一對象構造器)。這個限制可以防止我們在為值類型定義了一個更復雜的,完成了重要準備構造器之后,別人還說錯誤的使用了那個自動生成的構造器。

note:如果我們像通過默認構造器、逐一對象構造器以及我們自己定義的構造器為值類型創建實例,我們建議將自己定制的構造器寫到擴展(extension)中,而不是跟值類型定義混在一起。

struct Point {
    var x = 0.0 , y = 0.0
}

struct Size {
    var width = 0.0, height = 0.0
}

struct  Rect {
    
    var origin = Point()
    var size = Size()

    init(){
        
    }
    
    init(origin:Point,size:Size){
        self.origin = origin
        self.size = size
    }
    
    init(center:Point,size:Size){
        
        let originX = center.x - size.width / 2.0
        let originY = center.y - size.height / 2.0
        
        let origin = Point(x: originX, y: originY)
        
        self.init(origin:origin,size:size)
    }
    
}

類的繼承和構造過程

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

Swift提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。

指定構造器和便利構造器

1.指定構造器 是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,并根據父類鏈往上調用父類的構造器來實現父類的初始化。

每一個類都必須擁有至少一個指定構造器。在某些情況下,許多類通過繼承了父類中的指定構造器而滿足了這個條件。

2.便利構造器時類中比較次要的,輔助型的構造器。我們可以定義便利構造器來調用同一個類中指定構造器,并為其參數提供默認值。我們也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。

我們應當只在必要的時候為類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間并讓類的構造過程更清晰明了。

自定構造器和便利構造器的語法

類的指定構造器的寫法跟值類型簡單構造器一樣:

init(parameters){
    //statements
}

便利構造器也采用相同樣式的寫法,但需要在init關鍵字前放置convenience關鍵字

convenience init(parameters){
    //statements
}

類的構造器代理規則

為了簡化指定構造器和便利構造器之間的調用關系,Swift采用以下三條規則來限制構造器之間的代理調用:

  • 指定構造器必須調用其直接父類的指定構造器。
  • 便利構造器必須調用同一類中定義的其他構造器。
  • 便利構造器必須最終以調用一個指定構造器結束。

一個便于記憶的方法是:

  • 指定構造器必須宗師向上代理
  • 便利構造器必須總是橫行代理

兩段式構造過程

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

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

note:swift的兩段式構造過程跟Objective-c中的構造過程類似。最主要的區別在于階段 1,Objective-C給每一個屬性賦值0或空值(比如說nil)。Swift的構造流程則更加靈活,它允許哦嘛設置定制的初始值,并自如應對某些屬性不能以0或者nil作為合法默認值的情況。

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

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

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

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



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

階段1:

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

階段2:

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

構造器的繼承和重寫

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

note:父類的構造器僅在確定和安全的情況下被繼承。

假如我們希望自定義的子類中能實現一個或多個跟父類相同的構造器,也許是為了完成一些定制的構造過程,我們可以在定制的子類中提供和重寫父類型相同的構造器。

當我們重寫一個父類中帶有指定構造器的子類構造器時,我們需要重寫這個指定的構造器。因此,我們必須在定義子類構造器時帶上override修飾符。即使我們重寫系統提供的默認構造器也需要帶上override修飾符。

相反地,如果你寫了一個和父類便利構造器相匹配的子類構造器,子類都不能直接調用父類的便利構造器,每個規則都是在上文類的構造器代理規則有所描述。因此,我們的子類不必(嚴格意義上來講)提供了一個父類構造器的重寫。這樣的結果就是,你不需要在子類中提供一個匹配的父類便利構造器實現。

自動構造器的繼承

如上所述,子類不會默認繼承父類的構造器。但是如果特定條件可以滿足,父類構造器時可以被自動繼承的。在實踐中,這意味著對許多常見場景你不必重寫父類的構造器,并且盡可能安全的情況下以最小的代價來繼承父類的構造器。

假設要為子類中引入的任何新屬性提供默認值:

  • 如果子類沒有定義任何構造器,它將自動繼承所有父類的指定構造器。
  • 如果子類提供了所有父類指定構造器的實現---不管時同喲規則1繼承過來的,還說通過自定義實現的---它將自動繼承所有父類的便利構造器。

指定構造器和便利構造器實例

接下來的例子將在操作中展示指定構造器、便利構造器和自動構造器的繼承。它定義了包含三個類Food、RecipeIngredient以及ShoppingListItem的類層次結構,并將演示它們的構造器是如何相互作用的。

類層次中的基類是Food,它是一個簡單的用來封裝食物名字的類。Food類引入了一個叫做name的String屬性,并提供了兩個構造器來創建Food實例。


class Food {
    
    var name: String
    init(name:String){
        self.name = name
    }
    
    convenience init(){
        self.init(name:"[unnamed]")
    }
}

類沒有提供一個默認的逐一成員構造器,所以Food提供了一個接受單一參數name的指定構造器。這個構造器可以使用一個特定的名字來創建新的Food實例

let nameMeat = Food(name:"bacon")

Food類中的構造器init(name:String)被定義為一個指定構造器,因為它能確保所有新Food實例中存儲型屬性都被初始化。Food類沒有父類,所以init(name:String)構造器不需要調用super.init()來完成構造。

Food類同樣提供了一個沒有參數的便利構造器init()。這個init()構造器為新食物提供了一個默認占位名字,通過代理調用同一類中定義的構造器init(name:String)并給參數name傳值[Unnamed]來實現:

let food = Food()

類層級中的第二個類是Food的子類RecipeIngredient。RecipeIngredient類構建了食譜中的一味調味劑。它引入了Int類型的數量屬性quantity,并且定義了兩個構造器來創建RecipeIngredient實例:

class RecipeIngredient: Food {
    var quantity: Int
    
    init(name: String,quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name:name,quantity: 1)
    }
}

RecipeIngredient類擁有一個指定構造器init(name:String, quantity:Int),它可以用來產生新的實例。這個構造器一開始先將傳入的quantity參數賦值給quantity屬性,這個屬性也是唯一在RecipeIngredient中新引入的屬性。隨后,構造器在任務向上代理給Food的init(name:String).這個過程滿足兩段式構造過程中的安全檢查1。

RecipeIngredient也定義了一個便利構造器init(name:String),它只通過name來創建RecipeIngredient實例。這個變量構造器假設任意RecipeIngredient實例的quantity為1,所以不需要顯示指明數量即可創建出實例。這個便利構造器的定義可以讓創建實例更加方便和快捷,并且避免了使用重復代碼創建多個quantity為1的實例。這個便利構造器之時簡單的將任務代理給了同一個類里提供的指定構造器。

在這個例子中,RecipeIngredient的父類是Food,它有一個便利構造器init()。這個構造器因此也被RecipeIngredient繼承。這個繼承的init()函數版本跟Food提供的版本一樣的,除了任務代理給RecipeIngredient版本的init(name:String)而不是Food提供的版本。

所有的這三種構造器都可以用來創建新的RecipeIngredient實例:

let one = RecipeIngredient()
let two = RecipeIngredient(name:"bacon")
let three = RecipeIngredient(name:"eggs",quantity:5)

類層次中第三個也是最后一個類是RecipeIngredient子類,叫做ShoppingListItem。這個類構建了購物單中出現的某一種調味料。

購物單中每一項都是從未購買狀態開始的。

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        
        return "\(quantity) x \(name)"
    }
}

由于它為自己引入的所有屬性都提供了默認值,并且自己沒有定義任何構造器,ShoppingListItem將自動繼承所有父類中指定構造器和便利構造器。

可失敗構造器

如果一個類、結構體或枚舉類型的對象,在構造自身的過程中有可能失敗,則為其定義一個可失敗的構造器,是非常有用的。這里所指的“失敗”是指,如給構造器傳入無效的參數值,或缺少某種所需的外部資源,又或者是不滿足某種必要的條件等。

為了妥善處理這種構造過程中可能會失敗的情況。我們可以給一個類,結構體或是枚舉類型的定義中,添加一個或多個可失敗構造器。其語法是在init關鍵字后面添加問號? init?

note:可失敗構造器的參數名和參數類型,不能與其它非可失敗構造器的參數名,及其類型相同。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

枚舉類型的可失敗構造器

我們可以通過構造一個帶一個或多個參數的可失敗構造器來獲取枚舉類型中特定的枚舉成員。還能在參數不滿足枚舉成員期望的條件時,構造失敗。

enum TemperatureUnit {
     case Kelvin, Celsius, Fahrenheit
     init?(symbol: Character) {
         switch symbol {
         case "K":
             self = .Kelvin
         case "C":
             self = .Celsius
         case "F":
             self = .Fahrenheit
         default:
            return nil 

            }
    } 

}

帶原始值的枚舉類型的可失敗構造器

帶原始值的枚舉類型會自帶一個可失敗構造器 init?(rawValue:)該可失敗構造器有一個名為rawValue
的默認參數,其類型和枚舉類型的原始值類型一只,如果該參數的值能夠和枚舉類型成員所帶的原始值匹配,該構造器構造一個帶此原始值的枚舉成員,否則構造失敗。

enum TemperatureUnit: Character {
     case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
 let fahrenheitUnit = TemperatureUnit(rawValue: "F")
 if fahrenheitUnit != nil {
     print("This is a defined temperature unit, so initialization succeeded.")
 }

類的可失敗構造器

值類型(如結構體或枚舉類型)的可失敗構造器,對何時何地觸發構造失敗這個行為沒有任何的限制。

而對類而言,就沒有那么幸運了。類的可失敗構造器只能在所有的類屬性被初始化后和所有類之間的構造器之間的代理調用發生完后觸發失敗行為。

 class Product {
     let name: String!
     init?(name: String) {
         self.name = name
         if name.isEmpty { return nil }
     }
}

構造失敗的傳遞

可失敗構造器允許在同一類,結構體和枚舉中橫向代理其他的可失敗的構造器。類似的,子類的可失敗構造器也能向上代理基類的可失敗構造器。

無論時向上代理還氏橫向代理,如果我們代理的可失敗構造器,在構造過程中觸發了構造失敗的行為,整個構造過程都將被立即終止,接下來任何的構造代碼都將不會被執行。

note:可失敗的構造器也可以代理調用其它的非可失敗構造器。通過這個方法,你可以為已有構造過程加入構造失敗的條件。


 class CartItem: Product {
     let quantity: Int!
     init?(name: String, quantity: Int) {
         self.quantity = quantity
         super.init(name: name)
         if quantity < 1 { return nil }

    } 

  }

重寫一個可失敗構造器

就如同其它構造器一樣,我們也可以用子類的可失敗構造器重寫基類的可失敗構造器。或者你也可以用子類的非可失敗構造器重寫一個基類的可失敗構造器。這樣做的好處是,即使基類的構造器為可失敗構造器,但當子類的構造器在構造過程不可能失敗時,我們也可以把它修改過來。

note:當我們用一個子類的非可失敗構造器重寫一個父類的可失敗構造器時,子類的構造器將不再能向上代理父類的可失敗構造器。一個非可失敗的構造器永遠也不能代理調用一個可失敗的構造器。
我們可以用一個非可失敗構造器重寫一個可失敗的構造器,但分過來卻行不通。

class Document {
    var name: String?
    
    init() {
       
    }
    
    init?(name: String) {
        self.name = name
        if name.isEmpty { return nil }
    }
}

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        } }
}

可失敗構造器init!

通常來說我們通過在init關鍵字后添加問號的方式(init?)來定義一個可失敗構造器,但我們也可以使用通過在init后面添加驚嘆號的方式來定義一個可失敗構造器(init!),該可失敗構造器將會構造一個特定類型的隱式解析可選類型的對象。

我可以在init?構造器中代理調用init!構造器,反之亦然。我們也可以用init?重寫init!,反之亦然。我們還可以用init代理調用init!,但這會觸發一個斷言;init!構造器是否會觸發構造失敗?

必要構造器

在類的構造器前添加required 修飾符表明所有該類的子類都必須實行該構造器:

 class SomeCalss {
    required init(){
        //在這里添加該必要構造器的實現代碼
    }
 
 }

在子類重寫父類的必須要構造器時,必須在子類的構造器前夜添加required修飾符,這是為了保證繼承鏈上子類構造器也是必要構造器。在重寫父類的必要構造器時,不需要加override修飾符:
note:如果子類繼承的構造器能滿足必要的構造器的需求,則無需顯示的在子類中提供必要構造器的實現。

class SomeSubClass : SomeClass {

    required init(){
        //statement 
    }
    
}

通過閉包和函數來設置屬性的默認值

如果某個存儲屬性的默認值需要特別的定制或準備,我們就可以使用閉包或全局函數來為其屬性提供定制的默認值。每當某個屬性所屬的新類型實例創建時,對應的閉包或函數會被調用,而它們的返回值會當做默認值賦值給這個屬性。

class SomeClass {

    let someProperty: SomeType = {
        //.........
        return someValue 
    }()

}

析構過程

析構器只適用于類類型,當一個類的實例被釋放前,析構器會被立即調用。析構器用關鍵字deinit來標示,類似于構造器要用init來標示。

析構過程原理

Swift會自動釋放不再需要的實例以釋放資源。Swift通過自動引用計數(ARC)處理實例的內存管理。通常當我們的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,我們可能需要進行一些額外的處理。比如,打開一個文件,哪么就需要手動去關閉一個文件。

在類的定義中,每個類最多只能有一個析構器,而且析構器不帶任何參數:

deinit {
    //執行析構過程
}

析構器時在實例釋放發生前被自動調用。析構器時不允許被主動調用的。子類繼承了負累的析構器,并且在子類析構器實現的最后,父類的析構器會被自動調用。即使子類沒有提供自己的析構器,父類的析構器也同樣會被調用。

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

析構器操作

class Animal {
    
    deinit{
        print("animal dead")
    }
    
}

class Dog: Animal{
    
    deinit{
        print("Dog dead")
    }
}

func doSomething() {
    
    _ = Dog()
    
}

doSomething()
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容