構(gòu)造過程
- 概念:在使用類、結(jié)構(gòu)體或者枚舉類型的實例之前的準(zhǔn)備過程稱之為構(gòu)造過程芽偏。
- 操作內(nèi)容:設(shè)置每個儲存型屬性的初始值和其他的必要的初始化工作
- 實現(xiàn)方式:定義構(gòu)造器
構(gòu)造器
- 概念:創(chuàng)建某個特定類型的新實例的一種特殊方法揽乱。
//最簡形式
init() {
//此處進行構(gòu)造過程
}
注意:Swift的構(gòu)造器無需返回值,OC有
存儲屬性的初始賦值
- 類和結(jié)構(gòu)體在創(chuàng)建實例時名眉,必須為所有存儲型屬性設(shè)置合適的初始值。存儲型屬性的值不能處于一個未知的狀態(tài)
- 賦值方式:
- 定義屬性時直接設(shè)置默認(rèn)值
- 在構(gòu)造器中為其賦初始值
//示例代碼:
struct Test {
let attribute = "bluajack"
}
struct Test {
let attribute: String
init(value: String) {
//attribute = "bluajack"
attribute = value
}
}
let t = Test(value: "bluajack")
- 枚舉是沒有存儲屬性的凰棉,但是有計算屬性损拢。2.常量屬性(let聲明)一旦賦值后,就不能更改了撒犀。在本類的常量屬性福压,其子類也不能修改這個常量屬性。
默認(rèn)構(gòu)造器
- 結(jié)構(gòu)體或類的所有屬性值都有默認(rèn)值或舞,沒有自定義構(gòu)造器荆姆,系統(tǒng)會自動提供一個默認(rèn)構(gòu)造器
//示例:
class Person {
let name = "bluajack"
var sex: String?//可選屬性默認(rèn)值為nil
}
let p = Person() //默認(rèn)構(gòu)造器 init(){}
結(jié)構(gòu)體的逐一成員構(gòu)造器
//示例
struct Test {
let attribute = "bluajack"
let attribute2: String
}
let t = Test(attribute2: "sb")
看到這里,有人或許會不明白映凳。不是說胆筒,所有的存儲屬性都必要要有合適的初始值么?那為什么attribute2沒有賦值诈豌,卻不報錯呢仆救?
那么下面我就介紹下,專屬于結(jié)構(gòu)體的————逐一成員構(gòu)造器
先舉例矫渔,后總結(jié)
//范例1
struct Test {
let attribute = "bluajack"
let attribute2: String
}
//1.結(jié)構(gòu)體test的存儲屬性全用常量聲明
//2.存儲屬性中有屬性沒有賦初始值
//結(jié)果
let t = Test(attribute2: "sb")
//自動生成一個逐一成員構(gòu)造器彤蔽,但只有attribute2參數(shù)
//范例2
struct Test2 {
let attribute = "bluajack"
let attribute2: String = "sb"
}
//1.結(jié)構(gòu)體test的存儲屬性全用常量聲明
//2.存儲屬性全部賦值完畢
//結(jié)果
let t2 = Test2()
//沒有生成逐一成員構(gòu)造器,只有生成了默認(rèn)的構(gòu)造器
//范例3
struct Test3 {
var attribute = "bluajack"
var attribute2: String = "sb"
}
//1.結(jié)構(gòu)體test的存儲屬性全用變量聲明
//2.存儲屬性全部賦值完畢
//結(jié)果
let t3 = Test3(attribute: "s", attribute2: "b")
let t_3 = Test3()
//生成了逐一成員構(gòu)造器庙洼,并且所有參數(shù)都存在铆惑,同時也生成了默認(rèn)構(gòu)造器
//范例4
struct Test4 {
var attribute = "bluajack"
var attribute2: String
}
//1.結(jié)構(gòu)體test的存儲屬性全用變量聲明
//2.存儲屬性中有屬性沒有賦初始值
//結(jié)果
let t4 = Test4(attribute: "s", attribute2: "b")
//生成了逐一成員構(gòu)造器,并且所有參數(shù)都存在,但沒有生成默認(rèn)構(gòu)造器
//范例5
struct Test5 {
let attribute = "bluajack"
var attribute2: String
}
let t5 = Test5(attribute2: "sb")
//范例6
struct Test6 {
var attribute = "bluajack"
let attribute2: String
}
let t6 = Test6(attribute: "s", attribute2: "b")
歸納
1.也就是說送膳,結(jié)構(gòu)體中,只要有變量存儲屬性存在丑蛤,并且沒有提供自定義的構(gòu)造器的話叠聋,那么一定會生成一個結(jié)構(gòu)體逐一成員構(gòu)造器。
2.結(jié)構(gòu)體中沒有變量存儲屬性存在受裹,沒有提供自定義的構(gòu)造器碌补,但常量存儲屬性沒有賦值的話虏束,也會生成一個結(jié)構(gòu)體逐一成員構(gòu)造器,但需要注意的是厦章,生成的構(gòu)造器中的構(gòu)造參數(shù)镇匀,只會與沒賦值的常量屬性一一對應(yīng)。
總結(jié)
總結(jié):結(jié)構(gòu)體未提供自定義構(gòu)造器 ->「 存在變量屬性 -> 生成
不存在變量屬性「 常量屬性全部賦值 ->不生成
存在常量屬性未賦值 -> 生成
」
值類型的構(gòu)造器代理
構(gòu)造器可以通過調(diào)用其他構(gòu)造器來完成實例的部分構(gòu)造過程袜啃。這個過程稱為構(gòu)造器代理汗侵,它能減少多個構(gòu)造器間的代碼重復(fù)
對于值類型,你可以使用self.init在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器群发。并且你只能在構(gòu)造器內(nèi)部調(diào)用self.init
千萬記住晰韵,這種橫向代理,只對值類型有效熟妓,類類型無效,類型有自己的規(guī)則雪猪。
- 如果你為某個值類型定義了一個自定義的構(gòu)造器,你將無法訪問到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體起愈,還將無法訪問逐一成員構(gòu)造器)只恨。這種限制可以防止你為值類型增加了一個額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯誤的使用自動生成的構(gòu)造器。
//示例
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 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)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
//第三個Rect構(gòu)造器init(center:size:)抬虽。它先通過center和size的值計算出origin的坐標(biāo)官觅,
//然后再調(diào)用(或者說代理給)init(origin:size:)構(gòu)造器來將新的origin和size值賦值到對應(yīng)的屬性中。
注意:假如你希望默認(rèn)構(gòu)造器斥赋、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實例缰猴,可以將自定義的構(gòu)造器寫到擴展(extension)中,而不是寫在值類型的原始定義中疤剑。
指定構(gòu)造器
- 簡介:類中最主要的構(gòu)造器滑绒,初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類的指定構(gòu)造器來實現(xiàn)父類的初始化隘膘。
- 每一個類都必須擁有至少一個指定構(gòu)造器疑故。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個條件弯菊。
便利構(gòu)造器
- 簡介:簡介:類中比較次要的纵势、輔助型構(gòu)造器。
- 作用:可以創(chuàng)建一個特殊用途或特定輸入值的實例管钳,某種情況下通過使用便利構(gòu)造器來快捷調(diào)用某個指定構(gòu)造器钦铁,能夠節(jié)省更多開發(fā)時間并讓類的構(gòu)造過程更清晰明了。
- 寫法:便利構(gòu)造器也采用相同樣式的寫法才漆,但需要在init關(guān)鍵字之前放置convenience關(guān)鍵字牛曹,并使用空格將它們倆分開
class A {
var a:String
//指定構(gòu)造器
init() { a = "123"}
//便利構(gòu)造器
convenience init(value: String) {
self.init()
}
}
class B:A {
//指定構(gòu)造器
init(hah: String) {
// super.init()
// a = hah
//如果我們不需要改變a的話,我們可以不用顯示的調(diào)用super.init()
//因為這里是初始化的最后了醇滥,Swift替我們自動完成了黎比。不過我一般還是加上超营,看的順眼
}
}
let b = B(hah: "567657")
print(b.a)
類的構(gòu)造器代理
- 1.指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。(沒父類就不用調(diào)用了)
- 2.便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器阅虫。(可以是便利構(gòu)造器演闭,也可以指定構(gòu)造器)
- 3.便利構(gòu)造器必須最終導(dǎo)致一個指定構(gòu)造器被調(diào)用。
總兒言之颓帝,言兒總之
指定構(gòu)造器必須總是向上代理
便利構(gòu)造器必須總是橫向代理
二段式構(gòu)造過程
- 階段一
- 某個指定構(gòu)造器或便利構(gòu)造器被調(diào)用米碰。
- 完成新實例內(nèi)存的分配,但此時內(nèi)存還沒有被初始化躲履。
- 指定構(gòu)造器確保其所在類引入的所有存儲型屬性都已賦初值见间。存儲型屬性所屬的內(nèi)存完成初始化。
- 指定構(gòu)造器將調(diào)用父類的構(gòu)造器工猜,完成父類屬性的初始化米诉。
- 這個調(diào)用父類構(gòu)造器的過程沿著構(gòu)造器鏈一直往上執(zhí)行,直到到達構(gòu)造器鏈的最頂部篷帅。
- 當(dāng)?shù)竭_了構(gòu)造器鏈最頂部史侣,且已確保所有實例包含的存儲型屬性都已經(jīng)賦值,這個實例的內(nèi)存被認(rèn)為已經(jīng)完全初始化魏身。此時階段 1 完成惊橱。
- 階段二
- 從頂部構(gòu)造器鏈一直往下,每個構(gòu)造器鏈中類的指定構(gòu)造器都有機會進一步定制實例箭昵。構(gòu)造器此時可以訪問self税朴、修改它的屬性并調(diào)用實例方法等等。
- 最終家制,任意構(gòu)造器鏈中的便利構(gòu)造器可以有機會定制實例和使用self正林。
構(gòu)造器的繼承和重寫
- Swift中的子類默認(rèn)情況下不會繼承父類的構(gòu)造器。
- 重寫父類的這個指定構(gòu)造器颤殴,必須帶上override修飾符觅廓。因為便利構(gòu)造器是橫向代理,子類不能調(diào)用父類的便利構(gòu)造器涵但。所以你在子類寫一個和父類便利構(gòu)造器一模一樣的方法杈绸。不需要加override,也不算重寫矮瘟。
- 當(dāng)你重寫一個父類的指定構(gòu)造器時瞳脓,你總是需要寫override修飾符,即使你的子類將父類的指定構(gòu)造器重寫為了便利構(gòu)造器澈侠。
class C {
init(a: String) {print("?")}
init(b: String) {}
}
class D:C {
override init(a: String) {
super.init(a: a)
}
override convenience init(b: String) {
self.init(a: b)
}
}
構(gòu)造器的自動繼承
- 子類在默認(rèn)情況下不會繼承父類的構(gòu)造器劫侧,滿足以下條件,父類的構(gòu)造器是可以被繼承的埋涧。
規(guī)則1:如果子類沒有定義任何指定構(gòu)造器板辽,它將自動繼承父類的所有指定構(gòu)造器。
規(guī)則2:如果子類提供了所有父類的指定構(gòu)造器的實現(xiàn)棘催,還是自定義重寫提供了實現(xiàn)(將父類的指定構(gòu)造器重寫為便利構(gòu)造器也可以)————它將自動繼承父類所有的便利構(gòu)造器劲弦。
- 即使你在子類中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用醇坝。
可失敗構(gòu)造器
- 構(gòu)造失斠毓颉:給構(gòu)造器傳入無效的參數(shù)值,缺少某種所需的外部資源呼猪,又或是不滿足某種必要的條件等画畅。為了妥善處理這種構(gòu)造過程中可能會失敗的情況。用可失敗構(gòu)造器即可宋距。語法:
init轴踱?
注意:可失敗構(gòu)造器不能與其他非可失敗構(gòu)造器同名。
- 可失敗構(gòu)造器創(chuàng)建的對象是自身類型的可選類型的對象谚赎∫В可以通過在構(gòu)造器里面 return nil來表示構(gòu)造失敗。
注意:嚴(yán)格來說壶唤,構(gòu)造器都不支持返回值雳灵。因為構(gòu)造器本身的作用,只是為了確保對象能被正確構(gòu)造闸盔。因此你只是用return nil 表明可失敗構(gòu)造器構(gòu)造失敗悯辙,而不要用關(guān)鍵字return來表明構(gòu)造成功。
struct Person {
let name: String
init?(value: String) {
if value.isEmpty { return nil }
name = value
}
}
let p = Person(value: "bluajack")
//p的類型是 Person迎吵?而不是Person
if let someone = p {
print("一個名為\(someone.name)的Person對象被創(chuàng)建成功了躲撰!")
}
let p2 = Person(value: "")
//p2的類型是 Person?而不是Person
if p2 == nil {
print("Person對象初始化失敗")
}
枚舉類型的可失敗構(gòu)造器
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(value: Character) {
switch value {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
let a = TemperatureUnit(value: "F")
if a != nil {
print("這是一個定義溫度的單位钓觉,所以初始化成功")
}
let unknownUnit = TemperatureUnit(value: "X")
if unknownUnit == nil {
print("這不是一個定義溫度的單位茴肥,所以初始化失敗")
}
帶原始值的枚舉類型的可失敗構(gòu)造器
??:帶原始值的枚舉類型會自帶一個可失敗構(gòu)造器( init?(rawValue:) )荡灾,該可失敗構(gòu)造器有一個名為rawValue的參數(shù)瓤狐,其類型和枚舉類型的原始值的類型一致。
enum Temperatureunit2: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let b = Temperatureunit2(rawValue: "F")
if b != nil {
print("這是一個定義溫度的單位批幌,所以初始化成功")
}
let unknownunit = Temperatureunit2(rawValue: "X")
if unknownunit == nil {
print("這不是一個定義溫度的單位础锐,所以初始化失敗")
}
構(gòu)造失敗的傳遞
- 類、結(jié)構(gòu)體荧缘、枚舉可失敗構(gòu)造器可以橫向代理到類型中的其他可失敗構(gòu)造器皆警。類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器截粗。有點屌信姓,既可以橫向代理鸵隧,也可以向上代理。
- 無論是向上代理還是橫向代理意推,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗豆瘫,整個構(gòu)造過程將立即終止,接下來的任何構(gòu)造內(nèi)部的代碼都不會再被執(zhí)行菊值。
注意:可失敗構(gòu)造器也可以代理到其他的非可失敗構(gòu)造器外驱。通過這種方式,你可以增加一個可能的失敗狀態(tài)到現(xiàn)有的構(gòu)造過程中腻窒£怯睿可失敗構(gòu)造器和類的構(gòu)造器自動繼承規(guī)則一樣。
class Product {
let name: String
init?(value: String) {
if value.isEmpty { return nil }
name = value
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(value: name)
}
}
重寫一個可失敗構(gòu)造器
- 子類可以重寫父類的可失敗構(gòu)造器儿子。也可以用子類的非可失敗構(gòu)造器重寫一個父類的可失敗構(gòu)造器瓦哎,這樣,你可以定義一個不會構(gòu)造失敗的子類典徊,即使父類的構(gòu)造器允許構(gòu)造失敗杭煎。
注意:子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時,向上代理到 父類的可失敗構(gòu)造器的唯一方式是 對父類的可失敗構(gòu)造器的返回值進行強制解包卒落。
注意:非可失敗構(gòu)造器可以重寫可失敗構(gòu)造器羡铲,反過來不行。
class Document {
var name: String?
// 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為 nil 的 document 實例
init() {}
// 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為非空字符串的 document 實例
init?(value: String) {
if value.isEmpty { return nil }
name = value
}
}
class AutoDocument: Document {
override init() {
super.init()
name = "[Untitled]"
}
override init(value: String) {
super.init(value: value)!
}
}
let d = AutoDocument(value: "")
//發(fā)生運行時錯誤
必要構(gòu)造器
- 在類的構(gòu)造器前添加required修飾符表明所有子類都必須實現(xiàn)該構(gòu)造器儡毕,子類重寫父類的必要構(gòu)造器時也切,必須在子類的構(gòu)造器前也添加required修飾符。在重寫父類中必要的指定構(gòu)造器腰湾,不需要添加override修飾符雷恃。
class SomeClass {
required init() {
// 構(gòu)造器的實現(xiàn)代碼
}
}
class SomeSubclass: SomeClass {
required init() {
// 構(gòu)造器的實現(xiàn)代碼
}
}
注意:如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的要求,則無須在子類的必要構(gòu)造器中顯式提供父類必要構(gòu)造器的實現(xiàn)
通過閉包或函數(shù)設(shè)置屬性的默認(rèn)值
- 這種類型的閉包或函數(shù)通常會創(chuàng)建一個跟屬性類型相同的臨時變量费坊,然后修改它的值以滿足預(yù)期的初始狀態(tài)倒槐,最后返回這個臨時變量,作為屬性的默認(rèn)值附井。
class SomeClass {
let someProperty: SomeType = {
// 在這個閉包中給 someProperty 創(chuàng)建一個默認(rèn)值
// someValue 必須和 SomeType 類型相同
return someValue
}()
}
注意閉包結(jié)尾的大括號后面接了一對空的小括號讨越。這用來告訴 Swift 立即執(zhí)行此閉包。如果你忽略了這對括號永毅,相當(dāng)于將閉包本身作為值賦值給了屬性把跨,而不是將閉包的返回值賦值給屬性。
注意:如果你使用閉包來初始化屬性沼死,請記住在閉包執(zhí)行時着逐,實例的其它部分都還沒有初始化。這意味著你不能在閉包里訪問其它屬性,即使這些屬性有默認(rèn)值耸别。同樣健芭,你也不能使用隱式的self屬性,或者調(diào)用任何實例方法秀姐。