Swift-Tips

1.屬性字符串

let string = "Test Attributed Strings"
let attributedString = NSMutableAttributedString(string: string)

let firstAttributes: [String : Any] = [NSForegroundColorAttributeName: UIColor.blue, NSBackgroundColorAttributeName: UIColor.yellow, NSUnderlineStyleAttributeName: 1]
attributedString.addAttributes(firstAttributes, range: NSRange(location: 0, length: 3))

2.Optional 實(shí)現(xiàn)

enum OptionalValue<T> {
    case none
    case some(T)
}

var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

3.可選解析

你可以包含多個(gè)可選綁定或多個(gè)布爾條件在一個(gè) if 語句中腺占,只要使用逗號(hào)分開就行驱富。只要有任意一個(gè)可選綁定 的值為nil稳诚,或者任意一個(gè)布爾條件為false,則整個(gè)if條件判斷為false宠进,這時(shí)你就需要使用嵌套 if 條 件語句來處理屏箍,如下所示:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"
 
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}

4.隱式解析可選類型

當(dāng)可選類型被第一次賦值之后就可以確定之后一直有值的時(shí)候,隱式解析可選類型非常有用胸哥。

一個(gè)隱式解析可選類型其實(shí)就是一個(gè)普通的可選類型涯竟,但是可以被當(dāng)做非可選類型來使用,并不需要每次都使用 解析來獲取可選值空厌。下面的例子展示了可選類型 String 和隱式解析可選類型 String 之間的區(qū)別

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感嘆號(hào)來獲取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感嘆號(hào)

注意:
如果你在隱式解析可選類型沒有值的時(shí)候嘗試取值庐船,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。和你在沒有值的普通可選類型后面加一個(gè)驚嘆號(hào)一樣蝇庭。

你仍然可以把隱式解析可選類型當(dāng)做普通可選類型來判斷它是否包含值:

 if assumedString != nil {
     print(assumedString)
}
// 輸出 "An implicitly unwrapped optional string."

//你也可以在可選綁定中使用隱式解析可選類型來檢查并解析它的值:
 if let definiteString = assumedString {
     print(definiteString)
}
// 輸出 "An implicitly unwrapped optional string."

注意:
如果一個(gè)變量之后可能變成 nil 的話請不要使用隱式解析可選類型醉鳖。如果你需要在變量的生命周期中判斷是否是nil的話,請使用普通可選類型哮内。

5.錯(cuò)誤處理

相對于可選中運(yùn)用值的存在與缺失來表達(dá)函數(shù)的成功與失敗盗棵,錯(cuò)誤處理可以推斷失敗的原因,并傳播至程序的其他部分北发。

當(dāng)一個(gè)函數(shù)遇到錯(cuò)誤條件纹因,它會(huì)拋出一個(gè)錯(cuò)誤。調(diào)用函數(shù)的調(diào)用者能捕獲該錯(cuò)誤并進(jìn)行相應(yīng)處理琳拨。

一個(gè)函數(shù)可以通過在聲明中添加 throws 關(guān)鍵字來拋出錯(cuò)誤消息瞭恰。
當(dāng)你調(diào)用的函數(shù)能拋出錯(cuò)誤時(shí),應(yīng)該在表達(dá)式中前置 try 關(guān)鍵字

func canThrowAnError() throws { // 這個(gè)函數(shù)有可能拋出錯(cuò)誤
}

do {
    try canThrowAnError() // 沒有錯(cuò)誤消息拋出
} catch {
    // 有一個(gè)錯(cuò)誤消息拋出
}

6.空和運(yùn)算符號(hào) ??

空合運(yùn)算符 ( a ?? b ) 將對 可選類型 a 進(jìn)行空判斷狱庇,如果 a 包含一個(gè)值就進(jìn)行解封惊畏,否則就返回一個(gè)默認(rèn)
b 。表達(dá)式 a 必須是 Optional 類型密任。默認(rèn)值 b 的類型必須要和 a 存儲(chǔ)值的類型保持一致颜启。

空合運(yùn)算符是對以下代碼的簡短表達(dá)方法:

a != nil ? a! : b

注意: 如果 a 為非空值( non-nil ),那么值 b 將不會(huì)被計(jì)算浪讳。這也就是所謂的短路求值

7.字符串

Swift 的 String 是值類型 如果您創(chuàng)建了一個(gè)新的字符串缰盏,那么當(dāng)其進(jìn)行常量、變量賦值操作,或在函數(shù)/ 方法中傳遞時(shí)口猜,會(huì)進(jìn)行值拷貝负溪。 任何情況下,都會(huì)對已有字符串值創(chuàng)建新副本济炎,并對該新副本進(jìn)行傳遞或賦值操作川抡。

Swift 默認(rèn)字符串拷貝的方式保證了在函數(shù)/方法中傳遞的是字符串的值。 很明顯無論該值來自于哪里冻辩,都是您獨(dú)自擁有的猖腕。 您可以確保傳遞的字符串不會(huì)被修改,除非你自己去修改它恨闪。

在實(shí)際編譯時(shí)倘感,Swift 編譯器會(huì)優(yōu)化字符串的使用,使實(shí)際的復(fù)制只發(fā)生在絕對必要的情況下 咙咽,這意味著在將字符串作為值類型的同時(shí)可以獲得極高的性能老玛。

遍歷字符串:
您可通過 for-in 循環(huán)來遍歷字符串中的 characters 屬性 來獲取每一個(gè)字符的值:

//Swif3 中通過 `.characters`屬性來遍歷
for character in "Dog!??". characters {
    print(character)
}
// D
// o
// g
// !
// ??

//Swift4 中直接通過 string 進(jìn)行遍歷
for character in "Dog!??" {
    print(character)
}
// D
// o
// g
// !
// ??

計(jì)算字符數(shù)量:

// Swift3
let unusualMenagerie = "Koala ??, Snail ??, Penguin ??, Dromedary ??" print("unusualMenagerie has \(unusualMenagerie.characters.count) characters") // 打印輸出 "unusualMenagerie has 40 characters"

//Swift4
let unusualMenagerie = "Koala ??, Snail ??, Penguin ??, Dromedary ??"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"

使用 characters 屬性的 indices 屬性會(huì)創(chuàng)建一個(gè)包含全部索引的范圍(Range),用來在一個(gè)字符串中訪問單 個(gè)字符钧敞。

 let greeting = "Guten Tag!"

// Swift3
 for index in greeting.characters.indices {
    print("\(greeting[index]) ", terminator: "")
}
// 打印輸出 "G u t e n T a g ! "

// Swift4
for index in greeting.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n   T a g ! "

8.多行字符串 Swift4

如果需要跨多行的字符串蜡豹,請使用多行字符串文字。多行字符串文字是由三個(gè)雙引號(hào)包圍的字符序列:

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.
 
"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

因?yàn)槎嘈懈袷绞褂萌齻€(gè)雙引號(hào)而不是僅一個(gè)溉苛,所以可以在多行字符串文字中包含一個(gè)雙引號(hào)( ")镜廉,如上面的示例所示。要 在多行字符串中包含 """ 文字愚战,必須使用反斜杠( \ )來轉(zhuǎn)義至少一個(gè)引號(hào)娇唯。例如:

let threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""

在其多行字符串中,字符串文字包括其開頭和結(jié)尾引號(hào)之間的所有行寂玲。該字符串在開頭的引號(hào)( """ )之后的第一行開始塔插,并在結(jié)束引號(hào)( """ )之前的行結(jié)束,這意味著quotation不會(huì)以換行符開頭或結(jié)尾拓哟。以下兩個(gè)字符串相同:

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

要使用換行符開頭或結(jié)尾的多行字符串文字想许,請將空行寫為第一行或最后一行。例如:

"""
 
This string starts with a line feed.
It also ends with a line feed.
 
"""

9. 遍歷數(shù)組

如果我們同時(shí)需要每個(gè)數(shù)據(jù)項(xiàng)的值和索引值断序,可以使用 enumerated() 方法來進(jìn)行數(shù)組遍歷流纹。 enumerated() 返回 一個(gè)由每一個(gè)數(shù)據(jù)項(xiàng)索引值和數(shù)據(jù)值組成的元組。我們可以把這個(gè)元組分解成臨時(shí)常量或者變量來進(jìn)行遍歷:

var list = ["Six eggs", "Milk", "Flour", "Baking Powder", "Bananas"]

for item in list {
    print(item)
}

// 同時(shí)遍歷每個(gè)數(shù)據(jù)項(xiàng)的索引和值
for (index, value) in list.enumerated() {
     print("Item \(index + 1): \(value)")
}

10.集合

合類型的哈希值
一個(gè)類型為了存儲(chǔ)在集合中违诗,該類型必須是 可哈嫌喾穑化 的--也就是說购桑,該類型必須提供一個(gè)方法來計(jì)算它的哈希值厢漩。一個(gè)哈希值是 Int 類型的兵睛,相等的對象哈希值必須相同杖挣,比如 a==b ,因此必須 a.hashValue == b.hashValue

Swift 的所有基本類型(比如 String , Int , DoubleBool )默認(rèn)都是可哈舷揍#化的负甸,可以作為集合的值的類型或者字典的鍵的類型。沒有 關(guān)聯(lián)值 的枚舉成員值默認(rèn)也是可哈仙骶粒化的贮尖。

注意: 你可以使用你自定義的類型作為集合的值的類型或者是字典的鍵的類型,但你需要使你的自定義類型符合 Swift 標(biāo)準(zhǔn)庫中的 Hashable 協(xié)議趁怔。符合 Hashable 協(xié)議的類型需要提供一個(gè)類型為 Int 的可讀屬性 hashValue 湿硝。由類型的 hashValue 屬性返回的值不需要在同一程序的不同執(zhí)行周期或者不同程序之間保持相同。
因?yàn)?Hashable 協(xié)議符合 Equatable 協(xié)議润努,所以遵循該協(xié)議的類型也必須提供一個(gè)"是否相等"運(yùn)算符( == )的實(shí) 現(xiàn)关斜。這個(gè) Equatable 協(xié)議要求任何符合 == 實(shí)現(xiàn)的實(shí)例間都是一種相等的關(guān)系。也就是說铺浇,對于 a,b,c 三個(gè)值來 說痢畜, == 的實(shí)現(xiàn)必須滿足下面三種情況:
? a == a (自反性)
? a == b 意味著 b == a (對稱性)
? a == b && b == c 意味著 a == c (傳遞性)

e.g:

沒有遵循 HashableAClass 無法放到集合中

class AClass {
    var name: String = "a"
}

let c1 = AClass()
let c2 = AClass()

var sets: Set = [c1, c2]
// error cannot convert value of type '[AClass]' to specified type 'Set'

遵循了 HashableBClass 可放到集合中

class BClass {
    var name: String = "a"
}

extension BClass: Hashable {
    var hashValue: Int {
        return name.hashValue
    }
    
    static func ==(lhs: BClass, rhs: BClass) -> Bool {
        return lhs.name == rhs.name
    }
}

let b1 = BClass()
let b2 = BClass()

var sets: Set = [b1, b2]

11.Switch

不存在隱式的貫穿
與 C 和 Objective-C 中的 switch 語句不同,在 Swift 中鳍侣,當(dāng)匹配的 case 分支中的代碼執(zhí)行完畢后丁稀,程序會(huì)終止 switch 語句,而不會(huì)繼續(xù)執(zhí)行下一個(gè) case 分支倚聚。這也就是說线衫,不需要在 case 分支中顯式地使用 break 語 句。這使得 switch 語句更安全惑折、更易用授账,也避免了因忘記寫 break 語句而產(chǎn)生的錯(cuò)誤。

每一個(gè) case 分支都必須至少包含一條語句唬复,下面這樣的代碼是錯(cuò)誤的矗积,因?yàn)榈谝粋€(gè) case 分支是空的

let anotherCharacter: Character = "a" switch anotherCharacter {
case "a": // 無效,這個(gè)分支下面沒有語句 case "A":
     print("The letter A")
 default:
     print("Not the letter A")
 }
// 這段代碼會(huì)報(bào)編譯錯(cuò)誤

為了讓單個(gè) case 同時(shí)匹配 aA 敞咧,可以將這兩個(gè)值組合成一個(gè) 復(fù)合匹配 棘捣,并用逗號(hào)隔開:

 let anotherCharacter: Character = "a"
 switch anotherCharacter {
 case "a", "A":
     print("The letter A")
 default:
     print("Not the letter A")
 }
// 輸出 "The letter A

區(qū)間匹配
case 分支的模式也可以是一個(gè)值的區(qū)間。下面的例子展示了如何使用區(qū)間匹配來輸出任意數(shù)字對應(yīng)的自然語言格式:

 let approximateCount = 62
 let countedThings = "moons orbiting Saturn"
 var naturalCount: String
 switch approximateCount {
 case 0:
     naturalCount = "no"
 case 1..<5:
     naturalCount = "a few"
 case 5..<12:
     naturalCount = "several"
 case 12..<100:
     naturalCount = "dozens of"
 case 100..<1000:
     naturalCount = "hundreds of"
 default:
     naturalCount = "many"
 }
print("There are \(naturalCount) \(countedThings).") // 輸出 "There are dozens of moons orbiting Saturn."

元組
我們可以使用元組在同一個(gè) switch 語句中測試多個(gè)值休建。元組中的元素可以是值乍恐,也可以是區(qū)間。另外测砂,使用下劃 線( _ )來匹配所有可能的值茵烈。

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box”

值綁定
case 分支允許將匹配的值綁定到一個(gè)臨時(shí)的常量或變量,并且在case分支體內(nèi)使用 —— 這種行為被稱為值綁定(value binding)砌些,因?yàn)槠ヅ涞闹翟赾ase分支體內(nèi)呜投,與臨時(shí)的常量或變量綁定

 let anotherPoint = (2, 0)
 switch anotherPoint {
 case (let x, 0):
     print("on the x-axis with an x value of \(x)")
 case (0, let y):
     print("on the y-axis with a y value of \(y)")
 case let (x, y):
     print("somewhere else at (\(x), \(y))")
 }
// 輸出 "on the x-axis with an x value of 2"

Where
case 分支的模式可以使用 where 語句來判斷額外的條件

 let yetAnotherPoint = (1, -1)
 switch yetAnotherPoint {
 case let (x, y) where x == y:
     print("(\(x), \(y)) is on the line x == y")
 case let (x, y) where x == -y:
     print("(\(x), \(y)) is on the line x == -y")
 case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// 輸出 "(1, -1) is on the line x == -y"

復(fù)合匹配
當(dāng)多個(gè)條件可以使用同一種方法來處理時(shí)加匈,可以將這幾種可能放在同一個(gè) case 后面,并且用逗號(hào)隔開仑荐。當(dāng) case 后面的任意一種模式匹配的時(shí)候雕拼,這條分支就會(huì)被匹配。并且粘招,如果匹配列表過長啥寇,還可以分行書寫:

 let someCharacter: Character = "e"
 switch someCharacter {
 case "a", "e", "i", "o", "u":
     print("\(someCharacter) is a vowel")
 case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
      "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
     print("\(someCharacter) is a consonant")
 default:
     print("\(someCharacter) is not a vowel or a consonant")
}
// 輸出 "e is a vowel"

let stillAnotherPoint = (9, 0)
 switch stillAnotherPoint {
 case (let distance, 0), (0, let distance):
     print("On an axis, \(distance) from the origin")
 default:
     print("Not on an axis")
 }
// 輸出 "On an axis, 9 from the origin"

12. fallthrough 貫穿

fallthrough 關(guān)鍵字不會(huì)檢查它下一個(gè)將會(huì)落入執(zhí)行的 case 中的匹配條件。 fallthrough 簡單地使代 碼繼續(xù)連接到下一個(gè) case 中的代碼洒扎,這和 C 語言標(biāo)準(zhǔn)中的 switch 語句特性是一樣的辑甜。

let character = "a"
switch character {
case "a":
    print("One")
    fallthrough
case "b":
    print("Two ")
default:
    print("default")
}

//One
//Two 

13. 帶標(biāo)簽的語句

在 Swift 中,你可以在循環(huán)體和條件語句中嵌套循環(huán)體和條件語句來創(chuàng)造復(fù)雜的控制流結(jié)構(gòu)袍冷。并且磷醋,循環(huán)體和條件語句都可以使用 break 語句來提前結(jié)束整個(gè)代碼塊。因此难裆,顯式地指明 break 語句想要終止的是哪個(gè)循環(huán)體或者條件語句會(huì)很有用子檀。類似地,如果你有許多嵌套的循環(huán)體乃戈,顯式指明 continue 語句想要影響哪一個(gè)循環(huán)體也會(huì)非常有用褂痰。

為了實(shí)現(xiàn)這個(gè)目的,你可以使用標(biāo)簽 (statement label) 來標(biāo)記一個(gè)循環(huán)體或者條件語句症虑,對于一個(gè)條件語 句缩歪,你可以使用 break 加標(biāo)簽的方式來結(jié)束這個(gè)被標(biāo)記的語句。對于一個(gè)循環(huán)語句谍憔,你可以使用 break 或者 continue 加標(biāo)簽匪蝙,來結(jié)束或者繼續(xù)這條被標(biāo)記語句的執(zhí)行。

聲明一個(gè)帶標(biāo)簽的語句是通過在該語句的關(guān)鍵詞的同一行前面放置一個(gè)標(biāo)簽习贫,作為這個(gè)語句的前導(dǎo)關(guān)鍵字 (introd ucor keyword) 逛球,并且該標(biāo)簽后面跟隨一個(gè)冒號(hào)。下面是一個(gè)針對 while 循環(huán)體的標(biāo)簽語法苫昌,同樣的規(guī)則適用于所有的循環(huán)體和條件語句颤绕。

label name : while condition { statements }

let loop1 = 0...5
let loop2 = 0...5

loop1: for l1 in loop1 {
    print("loop1")
    loop2: for l2 in loop2 {
        print("loop2")
        if l2 == 3 {
            break loop1 //顯式的結(jié)束loop1循環(huán)
        }
    }
}

/* 輸出
loop1
loop2
loop2
loop2
loop2
*/

14. 枚舉

關(guān)聯(lián)值
你可以定義 Swift 枚舉來存儲(chǔ)任意類型的關(guān)聯(lián)值,如果需要的話祟身,每個(gè)枚舉成員的關(guān)聯(lián)值類型可以各不相同奥务。枚舉的這種特性跟其他語言中的 可識(shí)別聯(lián)合(discriminated unions)標(biāo)簽聯(lián)合(tagged unions) 袜硫,或者 變體(variants) 相似氯葬。

e.g.
定義一個(gè)名為 Barcode 的枚舉類型,它的一個(gè)成員值是具有 (Int婉陷,Int帚称,Int官研,Int) 類型關(guān)聯(lián)值的 upc ,另一個(gè) 成員值是具有 String 類型關(guān)聯(lián)值的 qrCode 闯睹。

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

switch 中提取關(guān)聯(lián)值:

你可以在 switchcase 分支代碼中提取每個(gè)關(guān)聯(lián)值作為一個(gè)常量(用 let 前綴)或者 作為一個(gè)變量(用 var 前綴)來使用:

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."

如果一個(gè)枚舉成員的所有關(guān)聯(lián)值都被提取為常量阀参,或者都被提取為變量,為了簡潔瞻坝,你可以只在成員名稱前標(biāo)注一個(gè) let 或者 var:

 switch productBarcode {
 case let .upc(numberSystem, manufacturer, product, check):
     print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
 case let .qrCode(productCode):
     print("QR code: \(productCode).")
 }
// 輸出 "QR code: ABCDEFGHIJKLMNOP."

原始值

作為關(guān)聯(lián)值的替代選擇,枚舉成員可以被默認(rèn)值(稱為原始值 raw values )預(yù)填充杏瞻,這些原始值的類型必須相同所刀。

Note: 要設(shè)置原始值,枚舉必須繼承自一個(gè)類型:
原始值可以是字符串捞挥,字符浮创,或者任意整型值或浮點(diǎn)型值。每個(gè)原始值在枚舉聲明中必須是唯一的砌函。

// 這樣會(huì)編譯錯(cuò)誤
enum ASCIIControlCharacter {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

// 需要繼承自一個(gè)類型斩披,如 Character 才可以設(shè)置原始值
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

注意
原始值和關(guān)聯(lián)值是不同的。原始值是在定義枚舉時(shí)被預(yù)先填充的值讹俊,像上述三個(gè) ASCII 碼垦沉。對于一個(gè)特定的枚舉成員,它的原始值始終不變仍劈。關(guān)聯(lián)值是創(chuàng)建一個(gè)基于枚舉成員的常量或變量時(shí)才設(shè)置的值厕倍,枚舉成員的關(guān)聯(lián)值可以變化。

原始值的隱式賦值

在使用原始值為整數(shù)或者字符串類型的枚舉時(shí)贩疙,不需要顯式地為每一個(gè)枚舉成員設(shè)置原始值讹弯,Swift 將會(huì)自動(dòng)為你賦值。
例如这溅,當(dāng)使用整數(shù)作為原始值時(shí)组民,隱式賦值的值依次遞增 1 。如果第一個(gè)枚舉成員沒有設(shè)置原始值悲靴,其原始值將為0臭胜。

enum Planet: Int {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let p = Planet.mercury
p.rawValue
// p.rawValue = 0

當(dāng)使用字符串作為枚舉類型的原始值時(shí),每個(gè)枚舉成員的隱式原始值為該枚舉成員的名稱对竣。

 enum CompassPoint: String {
     case north, south, east, west
}
let earthsOrder = Planet.earth.rawValue // earthsOrder 值為 3
let sunsetDirection = CompassPoint.west.rawValue // sunsetDirection 值為 "west"

使用原始值初始化枚舉實(shí)例

如果在定義枚舉類型的時(shí)候使用了原始值庇楞,那么將會(huì)自動(dòng)獲得一個(gè)初始化方法,這個(gè)方法接收一個(gè)叫做 rawValue 的參數(shù)否纬,參數(shù)類型即為原始值類型吕晌,返回值則是枚舉成員或 nil 。你可以使用這個(gè)初始化方法來創(chuàng)建一個(gè)新的枚舉實(shí)例临燃。

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 類型為 Planet? 值為 Planet.uranus

然而睛驳,并非所有 Int 值都可以找到一個(gè)匹配的行星烙心。因此,原始值構(gòu)造器總是返回一個(gè) 可選 的枚舉成員乏沸。在上面的例子中淫茵, possiblePlanetPlanet? 類型,或者說“可選的 Planet ”蹬跃。

注意
原始值構(gòu)造器是一個(gè)可失敗構(gòu)造器匙瘪,因?yàn)椴⒉皇敲恳粋€(gè)原始值都有與之對應(yīng)的枚舉成員。

遞歸枚舉

遞歸枚舉是一種枚舉類型蝶缀,它有一個(gè)或多個(gè)枚舉成員使用該枚舉類型的實(shí)例作為關(guān)聯(lián)值丹喻。使用遞歸枚舉時(shí),編譯器會(huì)插入一個(gè)間接層翁都。你可以在枚舉成員前加上 indirect 來表示該成員可遞歸碍论。

例如,下面的例子中柄慰,枚舉類型存儲(chǔ)了簡單的算術(shù)表達(dá)式:

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

你也可以在枚舉類型開頭加上 indirect 關(guān)鍵字來表明它的所有成員都是可遞歸的:

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

/**
上面定義的枚舉類型可以存儲(chǔ)三種算術(shù)表達(dá)式:純數(shù)字鳍悠、兩個(gè)表達(dá)式相加、兩個(gè)表達(dá)式相乘坐搔。
枚舉成員 addition 和 multiplication 的關(guān)聯(lián)值也是算術(shù)表達(dá)式——這些關(guān)聯(lián)值使得嵌套表達(dá)式成為可能藏研。
例如,表達(dá)式 (5 + 4) * 2 薯蝎,乘號(hào)右邊是一個(gè)數(shù)字遥倦,左邊則是另一個(gè)表達(dá)式。
因?yàn)閿?shù)據(jù)是嵌套的占锯,因而用來存儲(chǔ)數(shù)據(jù)的枚舉類型也需要支持這種嵌套——這意味著枚舉類型需要支持遞歸袒哥。
下面的代碼展示了使用 ArithmeticExpression 這個(gè)遞歸枚舉創(chuàng) 建表達(dá)式 (5 + 4) * 2
*/

 let five = ArithmeticExpression.number(5)
 let four = ArithmeticExpression.number(4)
 let sum = ArithmeticExpression.addition(five, four)
 let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

// 要操作具有遞歸性質(zhì)的數(shù)據(jù)結(jié)構(gòu),使用遞歸函數(shù)是一種直截了當(dāng)?shù)姆绞较浴@绫こ疲旅媸且粋€(gè)對算術(shù)表達(dá)式求值的函數(shù):
func evaluate(_ expression: ArithmeticExpression) -> Int {
     switch expression {
     case let .number(value):
         return value
     case let .addition(left, right):
         return evaluate(left) + evaluate(right)
     case let .multiplication(left, right):
         return evaluate(left) * evaluate(right)
     }
}
print(evaluate(product)) // 打印 "18"

15. 屬性

延遲存儲(chǔ)屬性

延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。在屬性聲明前使用 lazy 來標(biāo)示一個(gè)延遲存儲(chǔ)屬性艺演。

注意
必須將延遲存儲(chǔ)屬性聲明成變量(使用 var 關(guān)鍵字)却紧,因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過程完成之前必須要有初始值胎撤,因此無法聲明成延遲屬性晓殊。

延遲屬性很有用,當(dāng)屬性的值依賴于在實(shí)例的構(gòu)造過程結(jié)束后才會(huì)知道影響值的外部因素時(shí)伤提,或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí)巫俺,可以只在需要的時(shí)候計(jì)算它。

class DataImporter {
     /*
DataImporter 是一個(gè)負(fù)責(zé)將外部文件中的數(shù)據(jù)導(dǎo)入的類肿男。 這個(gè)類的初始化會(huì)消耗不少時(shí)間介汹。
*/
var fileName = "data.txt"
// 這里會(huì)提供數(shù)據(jù)導(dǎo)入功能 }
class DataManager {
lazy var importer = DataImporter() var data = [String]()
// 這里會(huì)提供數(shù)據(jù)管理功能
}
let manager = DataManager() manager.data.append("Some data") manager.data.append("Some more data")
// DataImporter 實(shí)例的 importer 屬性還沒有被創(chuàng)建

print(manager.importer.fileName)
// DataImporter 實(shí)例的 importer 屬性現(xiàn)在被創(chuàng)建了 // 輸出 "data.txt”

注意
如果一個(gè)被標(biāo)記為 lazy 的屬性在沒有初始化時(shí)就同時(shí)被多個(gè)線程訪問却嗡,則無法保證該屬性只會(huì)被初始化一 次。

存儲(chǔ)屬性和實(shí)例變量 Stored Properties and Instance Variable

Swift 中沒有實(shí)例變量這個(gè)概念

Objective-C 為類實(shí)例存儲(chǔ)值和引用提供兩種方法嘹承。除了 屬性 之外窗价,還可以使用 實(shí)例變量 作為屬性值的后端存儲(chǔ)。

Swift 編程語言中把這些理論統(tǒng)一用屬性來實(shí)現(xiàn)叹卷。Swift 中的屬性沒有對應(yīng)的實(shí)例變量撼港,屬性的后端存儲(chǔ)也無法直接訪問。這就避免了不同場景下訪問方式的困擾骤竹,同時(shí)也將屬性的定義簡化成一個(gè)語句餐胀。屬性的全部信息——包括命名、類型和內(nèi)存管理特征——都在唯一一個(gè)地方(類型定義中)定義瘤载。

計(jì)算屬性

除存儲(chǔ)屬性外,類卖擅、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性鸣奔。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè) getter 和一個(gè)可 選的 setter惩阶,來間接獲取和設(shè)置其他屬性或變量的值挎狸。

struct SomeStruct {
    var width = 0.0, height = 0.0
    var length: Double {
        get {
            return width + height
        }
        set(newLength) {
            width = newLength * 0.5
            height = newLength * 0.5
        }
    }
}

簡化 setter 聲明

如果計(jì)算屬性的 setter 沒有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱 newValue 断楷。下面是使用了簡化 setter 聲明的結(jié)構(gòu)體代碼:

struct SomeStruct {
    var width = 0.0, height = 0.0
    var length: Double {
        get {
            return width + height
        }
        set {
            width = newValue * 0.5
            height = newValue * 0.5
        }
    }
}

只讀計(jì)算屬性

只有 getter 沒有 setter 的計(jì)算屬性就是只讀計(jì)算屬性锨匆。只讀計(jì)算屬性總是返回一個(gè)值,可以通過點(diǎn)運(yùn)算符訪問冬筒,但不能設(shè)置新的值恐锣。

只讀計(jì)算屬性的聲明可以去掉 get 關(guān)鍵字和花括號(hào):

 struct Cuboid {
     var width = 0.0, height = 0.0, depth = 0.0
     var volume: Double {
         return width * height * depth
     }
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0) 
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
 // 打印 "the volume of fourByFiveByTwo is 40.0"

屬性觀察器

屬性觀察器 監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器舞痰,即使新值和當(dāng)前值相同的時(shí)候也不例外土榴。

可以為除了 延遲存儲(chǔ)屬性 之外的其他存儲(chǔ)屬性添加屬性觀察器,也可以通過重寫屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器响牛。你不必為非重寫的計(jì)算屬性添加屬性觀察器玷禽,因?yàn)榭梢酝ㄟ^它的 setter 直接監(jiān)控和響應(yīng)值的變化。

  • willSet 在新的值被設(shè)置之前調(diào)用
  • didSet 在新的值被設(shè)置之后立即調(diào)用

willSet 觀察器將 新的屬性值 作為常量參數(shù)傳入呀打,可以為該參數(shù)指定一個(gè)名稱矢赁,如果不指定參數(shù)名,則使用默認(rèn)參數(shù)名 newValue 表示

didSet 觀察器將 舊的屬性值 作為參數(shù)傳入贬丛,也可以為該參數(shù)指定一個(gè)參數(shù)名撩银,不指定則使用默認(rèn)參數(shù)名 oldValue 表示。 如果 在 didSet 方法中再次對該屬性賦值瘫寝,那么新值會(huì)覆蓋舊的值蜒蕾。

注意:
父類的屬性在子類的構(gòu)造器中被賦值時(shí)稠炬,它在父類中的觀察器會(huì)被調(diào)用,隨后才會(huì)調(diào)用子類的觀察器咪啡。在父類初始化方法調(diào)用之前首启,子類給屬性賦值時(shí),觀察器不會(huì)被調(diào)用撤摸。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("About to set totalSteps to \(newValue)")
        }
        
        didSet {
            if totalSteps > oldValue {
                print("Did set totalSteps:  \(totalSteps) oldValue:\(oldValue)")
            } else {
                // 可以再次對totalSteps 進(jìn)行賦值毅桃,并且不會(huì)觸發(fā)屬性觀察器
                totalSteps = 0
            }
        }
    }
}

16.在實(shí)例方法中修改值類型

結(jié)構(gòu)體和枚舉是 值類型 ,默認(rèn)情況下准夷,值類型的屬性不能再它的實(shí)例方法中被修改钥飞。

但是,如果確實(shí)需要在某個(gè)特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性衫嵌,可以為這個(gè)方法選擇 可變(mytating) 行為读宙,然后就可以在該方法內(nèi)部改變它的屬性。并且這個(gè)方法的任何改變都會(huì)在方法執(zhí)行結(jié)束時(shí)寫回到原始的結(jié)構(gòu)體中楔绞。 方法還可以給它隱含的 self 屬性賦值一個(gè)全新的實(shí)例结闸,這個(gè)新實(shí)例在方法結(jié)束時(shí)會(huì)替換現(xiàn)存實(shí)例。

要使用 可變 方法酒朵,將關(guān)鍵字 mutating 放到方法的 func 關(guān)鍵字之前就可以了:

也可以在可變方法中給 self 賦值桦锄,可變方法能夠賦給隱含屬性 self 一個(gè)全新的實(shí)例。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
    
    mutating func reChangeSelf() {
        self = Point(x: 0.0, y: 0.0)
    }
}

var p = Point(x: 1.0, y: 1.0)
p.moveBy(deltaX: 2.0, y: 3.0)
p.reChangeSelf()

17.類型方法

定義在類型本身上調(diào)用的方法叫做 類型方法 蔫耽。
在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 static 來指定類型方法结耀。類還要用關(guān)鍵字 class 來允許自雷重寫父類的方法實(shí)現(xiàn)。

在 Swift 中匙铡,可以為所有的 類图甜、結(jié)構(gòu)體和枚舉定義類型方法。每一個(gè)類型方法都能被他所支持的類型顯式調(diào)用鳖眼。

class SomeClass {
    // class 修飾的類型方法可在子類中重寫
    class func someTypeMethod() {
        print("Called someTypeMethod")
    }
    
    // static 修飾的類型方法不可在子類中重寫
    static func someTypeMethod2() {
        print("Called someTypeMethod2")
    }
}

SomeClass.someTypeMethod()
SomeClass.someTypeMethod2()

class SomeChildClass :SomeClass {
    override class func someTypeMethod() {
        print("Override Child someTypeMethod")
    }
}

18.下標(biāo)

下標(biāo)可以定義在類具则、結(jié)構(gòu)體和枚舉中,是訪問集合具帮,列表或序列中元素的快捷方式博肋。可以使用下標(biāo)的索引設(shè)置和獲取值蜂厅,而不需要再調(diào)用對應(yīng)的存取方法匪凡。如 通過下標(biāo)訪問一個(gè) Array 實(shí)例中的元素可以寫作 someArray[index] , 訪問 Dictionary 中的實(shí)例元素可以寫作 someDictionary[key] 掘猿。

一個(gè)類型可以定義多個(gè)下標(biāo)病游,通過不同索引類型進(jìn)行重載。 下標(biāo)不限于一維,可以定義具有多個(gè)參數(shù)的下標(biāo)滿足自定義類型的需求衬衬。

下標(biāo)語法

下標(biāo)允許通過在實(shí)例名稱后面的方括號(hào)中傳入一個(gè)或者多個(gè)索引值來對實(shí)例進(jìn)行存取买猖。

下標(biāo)語法
與實(shí)例方法類似,定義下標(biāo)使用 subscript 關(guān)鍵字滋尉,指定一個(gè)或多個(gè)輸入?yún)?shù)和返回類型玉控;下標(biāo)可以設(shè)定為讀寫或只讀, 這種行為由 gettersetter 實(shí)現(xiàn)狮惜,類似于計(jì)算屬性高诺。

subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }
}

newValue 的類型和下標(biāo)返回的類型相同。如同計(jì)算屬性碾篡,可以不指定 setter 的參數(shù)( newValue)虱而, 如果不指定參數(shù), setter 提供一個(gè)名為 newValue 的默認(rèn)參數(shù).

如同只讀計(jì)算屬性开泽,可以省略只讀下標(biāo)的 get 關(guān)鍵字:

subscript(index: Int) -> Int {
    // 返回一個(gè)適當(dāng)?shù)?Int 類型的值
}

下面代碼演示了只讀下標(biāo)的實(shí)現(xiàn)牡拇,這里定義了一個(gè) TimesTable 結(jié)構(gòu)體,用來表示傳入整數(shù)的乘法表:

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}

let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
//six times three is 18

19.繼承

基類: 不繼承于其他的類穆律,稱為基類诅迷。

Swift 中的類并不是從一個(gè)通用的基類繼承而來。如果不為定義的類指定一個(gè)超類的話众旗,這個(gè)類就自動(dòng)成為基類

訪問超類的方法、屬性及下標(biāo)

在合適的地方趟畏,可以通過使用 super 前綴來訪問超類版本的方法贡歧,屬性或下標(biāo):

  • 在方法 someMethod() 的重寫實(shí)現(xiàn)中,可以通過 super.someMethod() 來調(diào)用超類版本的 someMethod() 方法
  • 在屬性 somePropertygettersetter 的重寫實(shí)現(xiàn)中赋秀,可以通過 super.someProperty 來訪問超類版本的 someProperty 屬性
  • 在下標(biāo)的重寫中利朵,可以通過 super[someIndex] 來訪問超類版本的相同下標(biāo)

重寫屬性

可以重寫屬性的 實(shí)例屬性類型屬性 ,提供自己定制的 gettersetter 猎莲,或添加屬性觀察器使重寫的屬性可以觀察屬性值什么時(shí)候發(fā)生改變绍弟。

重寫屬性的 Getters 和 Setters

可以提供定制的 gettersetter 來重寫任意繼承來的屬性,無論繼承來的屬性是存儲(chǔ)屬性還是計(jì)算型屬性著洼。 子類并不知道繼承來的屬性是存儲(chǔ)型還是計(jì)算型的樟遣,它只知道繼承來的屬性會(huì)有一個(gè)名字和類型。 在重寫一個(gè)屬性時(shí)身笤,必須將它的名稱和類型都寫出來豹悬,這樣才能是編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的。

可以將一個(gè)繼承來的只讀屬性重寫為可讀寫屬性液荸。只需要在子類中提供 settergetter 的實(shí)現(xiàn)即可瞻佛。但是,你不能將一個(gè)繼承來的讀寫屬性重寫為一個(gè)只讀屬性娇钱。

class SomeClass {
    var name : String {
        get {
            return "SomeClass"
        }
    }
}

class SomeSubClass : SomeClass {
    /// 可以將繼承來的只讀屬性重寫為一個(gè)讀寫屬性
    override var name: String {
        get {
            return name
        }
        set {
            name = newValue
        }
    }
}

當(dāng)嘗試將繼承來的讀寫屬性重寫為只讀屬性時(shí)伤柄,編譯器會(huì)報(bào)錯(cuò)

class SomeClass {
    var name : String = "SomeClass"
}

class SomeSubClass : SomeClass {
    /// 不能將繼承來的讀寫屬性重寫為一個(gè)只讀屬性
    //error: cannot override mutable property with read-only property 'name'
    override var name: String {
        get {
            return name
        }
    }
}

注意:
如果你在重寫屬性中提供了 setter绊困,那么你也一定要提供 getter。如果你不想在重寫版本中的 getter 里修改繼承來的屬性值适刀,你可以直接通過 super.someProperty 來返回繼承來的值秤朗,其中 someProperty 是你要重寫的屬性的名字。

重寫屬性觀察器

可以通過重寫屬性為一個(gè)繼承來的屬性添加屬性觀察器蔗彤。這樣一來川梅,當(dāng)繼承來的屬性值發(fā)生改變時(shí),就會(huì)被通知到然遏。

class SomeClass {
    var name : String = "SomeClass"
}

class SomeSubClass : SomeClass {
    override var name: String {
        didSet {
            print("Did Set")
        }
        willSet {
            print("Will Set")
        }
    }
}

注意
你不可以為繼承來的常量存儲(chǔ)型屬性或繼承來的只讀計(jì)算型屬性添加屬性觀察器贫途。這些屬性的值是不可以被設(shè)置的,所以待侵,為它們提供 willSetdidSet 實(shí)現(xiàn)是不恰當(dāng)?shù)摹?br> 此外還要注意丢早,你不可以同時(shí)提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化秧倾,并且你已經(jīng)為那個(gè)屬性提供了定制的 setter怨酝,那么你在 setter 中就可以觀察到任何值變化了。

  • 子類中只重寫了 getter 也不能同時(shí)重寫屬性觀察器
    編譯報(bào)錯(cuò):error: willSet variable may not also have a get specifier
class SomeClass {
    var name : String = "SomeClass"
}

class SomeSubClass : SomeClass {
    override var name: String {
      //只重寫 get 則該屬性重寫為只讀屬性那先,不會(huì)調(diào)用 set 方法, 所以 重寫屬性觀察器的話會(huì)編譯錯(cuò)誤
        get {
            return super.name + "in subclass"
        }
        willSet {
            
        }
    }
}
  • 不能同時(shí)提供重寫的 setter 和重寫的屬性觀察器:
    編譯報(bào)錯(cuò):error: willSet variable may not also have a set specifier
class SomeClass {
    var name : String = "SomeClass"
}

class SomeSubClass : SomeClass {
    override var name: String {
        get {
            return super.name + "in subclass"
        }
        set {
            name = newValue + "in subclass"
        }        
        willSet {
            print("will set")
        }
    }
}

正確的做法是农猬,可以通過定制的 setter 觀察屬性的值變化

class SomeClass {
    var name : String = "SomeClass"
}

class SomeSubClass : SomeClass {
    override var name: String {
        get {
            return super.name + "in subclass"
        }
        set {
            print("副作用...")
            name = newValue + "in subclass"
        }
    }
}

防止重寫

通過把方法,屬性或下標(biāo)標(biāo)記為 final 來防止它們被重寫售淡,只需要在聲明關(guān)鍵字前加上 final 修飾符即 可(例如: final var 斤葱, final funcfinal class func 揖闸, 以及 final subscript )揍堕。

如果你重寫了帶有 final 標(biāo)記的方法,屬性或下標(biāo)汤纸,在編譯時(shí)會(huì)報(bào)錯(cuò)衩茸。在類擴(kuò)展中的方法,屬性或下標(biāo)也可以在 擴(kuò)展的定義里標(biāo)記為 final 的贮泞。

你可以通過在關(guān)鍵字 class 前添加 final 修飾符( final class )來將整個(gè)類標(biāo)記為 final 的楞慈。這樣的類是不可 被繼承的,試圖繼承這樣的類會(huì)導(dǎo)致編譯報(bào)錯(cuò)啃擦。

20.構(gòu)造過程

構(gòu)造過程 是使用類抖部、結(jié)構(gòu)體或枚舉類型的實(shí)例之前的準(zhǔn)備過程。在新實(shí)例可用之前必須執(zhí)行的過程议惰,具體操作包括:

  • 設(shè)置實(shí)例中每個(gè)存儲(chǔ)屬性的初始值
  • 執(zhí)行其他必須的設(shè)置或者初始化工作

通過定義 構(gòu)造器 來實(shí)現(xiàn)構(gòu)造過程慎颗,這些構(gòu)造器可用看做是用來創(chuàng)建特定類型新實(shí)例的特殊方法。 Swift 的構(gòu)造器無需返回值,它們的主要任務(wù)是確保新實(shí)例在第一次使用之前完成正確的初始化

存儲(chǔ)屬性初始賦值
結(jié)構(gòu)體 在創(chuàng)建實(shí)例時(shí)俯萎,必須為所有存儲(chǔ)屬性設(shè)置合適的初始值傲宜。存儲(chǔ)屬性的值不能處于一個(gè)位置的狀態(tài)。

// 直接設(shè)置默認(rèn)屬性值
class SomeClass {
    var name: String = "haha"
}

// 類SomeClass沒有設(shè)置默認(rèn)屬性值夫啊,編譯報(bào)錯(cuò) error: class 'SomeStruct' has no initializers
class SomeClass {
    var name: String
}

// 可以在構(gòu)造器中設(shè)置默認(rèn)屬性值
class SomeClass {
    var name: String
    init() {
        name = "initialName"
    }
}
// 直接設(shè)置默認(rèn)屬性值
struct SomeStruct {
    var name: String = "haha"
}

// 或通過默認(rèn)構(gòu)造器設(shè)置
struct SomeStruct {
    var name: String
}
// 結(jié)構(gòu)體SomeStruct 沒有設(shè)置默認(rèn)屬性值函卒,但是編譯器會(huì)自動(dòng)生成一個(gè)逐一成員構(gòu)造器
let someStruct = SomeStruct(name: "name")

//也可以構(gòu)造器中設(shè)置默認(rèn)屬性值
struct SomeStruct {
    var name: String
    init() {
        name = "haha"
    }
}
let someStruct = SomeStruct()

注意
在為存儲(chǔ)屬性設(shè)置默認(rèn)值或者在構(gòu)造器中為其賦值時(shí),它們的值是直接被設(shè)置的撇眯,不會(huì)觸發(fā)任何屬性觀察器

值類型的構(gòu)造器代理

構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實(shí)例的部分構(gòu)造過程报嵌。這一過程稱為構(gòu)造器代理,它能減少多個(gè)構(gòu)造器間
的代碼重復(fù)熊榛。

構(gòu)造器代理的實(shí)現(xiàn)規(guī)則和形式在值類型和類類型中有所不同锚国。值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過程相對簡單玄坦,因?yàn)樗鼈冎荒艽斫o自己的其它構(gòu)造器血筑。類則不同,它可以繼承自其它類這意味著類有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化煎楣。

對于值類型豺总,你可以使用 self.init 在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用 self.init 择懂。

如果你為某個(gè)值類型定義了一個(gè)自定義的構(gòu)造器喻喳,你將無法訪問到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體,還將無法訪問逐一成員構(gòu)造器)困曙。這種限制可以防止你為值類型增加了一個(gè)額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯(cuò)誤的使用自動(dòng)生成的構(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()
    
    // 使用默認(rèn)屬性來初始化
    init() {}
    
    // 使用指定的 origin 和 size 來初始化
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    
    // 使用指定的 center 和 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)
    }
}

注意
假如你希望默認(rèn)構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實(shí)例赂弓,可以將自定義的構(gòu)造器寫到擴(kuò)展( extension )中,而不是寫在值類型的原始定義中哪轿。

struct Rect {
    var origin = Point(x: 0.0, y: 0.0)
    var size = Size(width: 0.0, height: 0.0)
}

extension Rect {
    // 使用指定的 center 和 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)
    }
}

// 默認(rèn)構(gòu)造器
let r0 = Rect()

// 自定義構(gòu)造器
let r1 = Rect(center: Point(x: 1.0, y: 2.0), size: Size(width: 3.0, height: 4.0))

// 逐一構(gòu)造器
let r2 = Rect(origin: Point(x: 1.0, y: 2.0), size: Size(width: 3.0, height: 4.0))

21.類的繼承和構(gòu)造過程

類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值盈魁。

指定構(gòu)造器
指定構(gòu)造器是類中最主要的構(gòu)造器,一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性窃诉,并根據(jù)父類鏈往上調(diào)用父類的構(gòu)造器來實(shí)現(xiàn)父類的初始化杨耙。

每一個(gè)類都必須擁有至少一個(gè)指定構(gòu)造器

便利構(gòu)造器
便利構(gòu)造器是類中比較次要的飘痛、輔助型的構(gòu)造器珊膜。你可以定義便利構(gòu)造器來調(diào)用同一個(gè)類中的指定構(gòu)造器,并未其參數(shù)提供默認(rèn)值宣脉。也可以定義便利構(gòu)造器來創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例车柠。

類的指定構(gòu)造器和便利構(gòu)造器語法

指定構(gòu)造器語法:

init(parameters) {
    statements
}

便利構(gòu)造器語法: convenience

convenience init(parameters) {
    statements
}

22.通過閉包或函數(shù)設(shè)置屬性的默認(rèn)值

如果某個(gè) 存儲(chǔ)屬性 的默認(rèn)值需要一些定制或設(shè)置,可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值,每當(dāng)這個(gè)屬性所在的類型的新實(shí)例被創(chuàng)建時(shí)竹祷,對應(yīng)的閉包或函數(shù)會(huì)被調(diào)用谈跛,而他們的返回值會(huì)被當(dāng)做默認(rèn)值賦值給這個(gè)屬性。

這種類型的閉包或函數(shù)通常會(huì)創(chuàng)建一個(gè)跟屬性類型相同的臨時(shí)變量塑陵,然后修改它的值以滿足預(yù)期的初始黃臺(tái)感憾,最后返回這個(gè)臨時(shí)變量,作為屬性的默認(rèn)值令花。

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

class SomeSub: SomeClass {
    var age: String = {
        let age = "11"
        print("age = \(age)")
        return age
    }()
}

let sub = SomeSub(name: "haha")
//age = 11

注意閉包結(jié)尾的大括號(hào)后面接了一對空的小括號(hào)阻桅。這用來告訴 Swift 立即執(zhí)行此閉包。如果你忽略了這對括號(hào)兼都,相當(dāng)于將閉包本身作為值賦值給了屬性嫂沉,而不是將閉包的返回值賦值給屬性。

23.解決實(shí)例之間的強(qiáng)引用循環(huán)

類實(shí)例之間的強(qiáng)引用循環(huán):

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitialized" )
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    var tenant: Person?
    deinit {
        print("(Apartment \(unit) is being deinitialized )")
    }
}

var tom: Person?
var unit4A: Apartment?
tom = Person(name: "Tom")
unit4A = Apartment(unit: "4A")

tom?.apartment = unit4A
unit4A?.tenant = tom

tom = nil
unit4A = nil
//當(dāng)吧tom 和 unit4A 設(shè)置為nil 時(shí)俯抖,析構(gòu)函數(shù)均不會(huì)調(diào)用输瓜,強(qiáng)引用循環(huán)會(huì)一直阻止 Person 和 Apartment 類實(shí)例的銷毀,從而導(dǎo)致內(nèi)存泄漏

弱引用和無主引用

Swift 提供兩種方法來解決在使用類的屬性時(shí)所遇到的強(qiáng)引用循環(huán)問題:弱引用 (weak reference)和 無主引用 (unowned reference

弱引用和無主引用允許引用循環(huán)中的一個(gè)實(shí)例引用另一個(gè)實(shí)例而不保持強(qiáng)引用芬萍。 這樣實(shí)例之間能相互引用而不產(chǎn)生強(qiáng)引用循環(huán)尤揣。

當(dāng)其他的實(shí)例有更短的生命周期時(shí),使用弱引用柬祠,即當(dāng)其他實(shí)例先析構(gòu)時(shí)使用弱引用北戏。 在上面的例子中, 在一個(gè)公寓的聲明周期內(nèi)漫蛔,會(huì)存在某段時(shí)間沒有主人的情況优床,所以在 Apartment 類中使用弱引用來打破循環(huán)引用。 相反的瞬项,當(dāng)其他實(shí)例擁有相同或者更長的聲明周期的情況下地梨,應(yīng)該使用無主引用。

弱引用

弱引用指不會(huì)對其引用的實(shí)例保持強(qiáng)引用毯盈,因此不會(huì)阻止 ARC 銷毀被引用的實(shí)例剃毒。這個(gè)特性阻止了引用變?yōu)閺?qiáng)引用循環(huán)的一部分。在聲明屬性或變量時(shí)搂赋,在前面加上 weak 關(guān)鍵字表明這是一個(gè)弱引用赘阀。

由于弱引用并不會(huì)保持所引用的實(shí)例,即使引用存在脑奠,實(shí)例也有可能被銷毀基公。因此,ARC 會(huì)在引用的實(shí)例被銷毀后宋欺,自動(dòng)將其賦值為 nil轰豆。并且胰伍,因?yàn)槿跻迷试S他們的值在運(yùn)行時(shí)被賦值為 nil, 所以他們會(huì)被定義為可選類型變量而不是常量秒咨。

注意
當(dāng) ARC 設(shè)置弱引用為 nil 時(shí)喇辽,屬性觀察器不會(huì)被觸發(fā)

Apartmenttenant 屬性聲明成弱引用后, Person 實(shí)例依然保持對 Apartment 實(shí)例的強(qiáng)引用雨席,但是 Apartment 實(shí)例只持有對 Person 實(shí)例的弱引用菩咨。這意味著當(dāng)斷開 tom 變量所持有的強(qiáng)引用時(shí),再也沒有指向 Person 實(shí)例的強(qiáng)引用了陡厘, 由于再也沒有指向 Person 實(shí)例的強(qiáng)引用抽米,該實(shí)例 (tom) 會(huì)被銷毀

tom = nil
// Tom is being deinitialized

唯一剩下執(zhí)行 Apartment 實(shí)例的強(qiáng)引用來自 unit4A ,如果斷開這個(gè)強(qiáng)引用糙置,再也沒有指向 Apartment 實(shí)例的強(qiáng)引用了云茸, 該實(shí)例 (unit4A)也會(huì)被銷毀

unit4A = nil
//Apartment 4A is being deinitialized
class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitialized" )
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    weak var tenant: Person?
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var tom: Person?
var unit4A: Apartment?
tom = Person(name: "Tom")
unit4A = Apartment(unit: "4A")

tom?.apartment = unit4A
unit4A?.tenant = tom

tom = nil
// Tom is being deinitialized
unit4A = nil
//Apartment 4A is being deinitialized

無主引用
和弱引用類似,無主引用保持對一個(gè)實(shí)例的強(qiáng)引用谤饭。和弱引用不同的是無主引用在其他實(shí)例有相同或者更長的生命周期時(shí)使用标捺。你可以在聲明屬性或者變量時(shí),在前面加上關(guān)鍵字 unowned 表示這是一個(gè)無主引用揉抵。

無主引用通常被期望擁有值亡容。不過 ARC 無法再實(shí)例被銷毀后將無主引用設(shè)為 nil ,因?yàn)榉强蛇x類型的變量不允許被賦值為 nil 冤今。

重要
使用無主引用闺兢,你必須確保引用始終指向一個(gè)未被銷毀的實(shí)例。
如果你視圖在實(shí)例被銷毀后戏罢,訪問該實(shí)例的無主引用屋谭,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

下例定義了兩個(gè)類 CustomerCreditCard龟糕, 模擬了銀行客戶和信用卡桐磁。 這兩個(gè)類中,每一個(gè)都將和另外一個(gè)類的實(shí)例作為自身的屬性讲岁。這種關(guān)系可能會(huì)造成強(qiáng)引用循環(huán)我擂。

CustomerCreditCard 之間的關(guān)系與之前弱引用例子中的 ApartmentPerson 不同。 一個(gè)客戶可能有或者沒有信用卡催首,但是一張信用卡總是關(guān)聯(lián)值一個(gè)客戶扶踊。 為了表示這種關(guān)系泄鹏, Customer 類有一個(gè)可選類型的 card 屬性郎任,但是 CreditCard 類有一個(gè)非可選類型的 customer 屬性。

此外备籽,只能通過將一個(gè) number 值和 customer 實(shí)例傳遞給 CreditCard 的構(gòu)造函數(shù)的方式來創(chuàng)建 CreditCard 實(shí)例舶治。 這樣可以確保當(dāng)創(chuàng)建 CreditCard 實(shí)例時(shí)總是有一個(gè) customer 實(shí)例與之關(guān)聯(lián)分井。

由于信用卡總是關(guān)聯(lián)著一個(gè)客戶,因此將 customer 屬性定義為無主引用霉猛,可以避免引用循環(huán)

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

class CreditCard {
    let number: Int
    unowned let customer: Customer
    init(number: Int, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit {
        print("Card #\(number) is being deinitialized")
    }
}

var tom: Customer?
tom = Customer(name: "Tom")
tom?.card = CreditCard(number: 1234_5678_9012_3456, customer: tom!)

tom = nil
//Tom is being deinitialized
//Card #1234567890123456 is being deinitialized

在關(guān)聯(lián)兩個(gè)實(shí)例后尺锚, Customer 實(shí)例持有對 CreditCard 實(shí)例的強(qiáng)引用,而 CreditCard 實(shí)例持有對 Customer 實(shí)例的無主引用惜浅。

由于 customer 的無主醫(yī)用瘫辩,當(dāng)斷開 tom 變量持有的強(qiáng)引用時(shí),再也沒有指向 Customer 實(shí)例的強(qiáng)引用坛悉。由于再也沒有指向 Customer 實(shí)例的強(qiáng)引用伐厌,該實(shí)例會(huì)被銷毀。其后裸影,再也沒有指向 CreditCard 實(shí)例的強(qiáng)引用挣轨,該實(shí)例也被隨之銷毀了。

tom = nil
//Tom is being deinitialized
//Card #1234567890123456 is being deinitialized

注意
上面的例子展示了如何使用 安全的無主引用 轩猩。對于需要禁用運(yùn)行時(shí)的安全檢查情況(例如卷扮,出于性能方面的原因), Swift 還提供了 不安全的無主引用 均践。與所有不安全的操作一樣晤锹,你需要負(fù)責(zé)檢查代碼以確保其安全性。 可用通過 unowned(unsafe) 來聲明不安全無主引用浊猾。 如果你視圖在實(shí)例被銷毀后抖甘,訪問該實(shí)例的不安全無主引用,你的程序會(huì)嘗試訪問該實(shí)例之前所在的內(nèi)存地址葫慎,這是一個(gè)不安全的操作衔彻。

無主引用以及隱式解析可選屬性

上面兩個(gè)例子:

  • PersonApartment 的例子展示了兩個(gè)屬性都允許為 nil ,并會(huì)潛在的產(chǎn)生強(qiáng)引用循環(huán)偷办,這種場景適合用弱引用來解決
  • CustomerCreditCard 的例子展示了一個(gè)屬性的值允許為 nil艰额, 而另一個(gè)屬性的值不允許為 nil,這也可能會(huì)產(chǎn)生強(qiáng)引用循環(huán)椒涯,這種場景適合通過無主引用來解決柄沮。

然而,存在著第三種場景废岂,在這種場景中祖搓,兩個(gè)屬性都必須有值,并且初始化后永遠(yuǎn)不會(huì)為 nil湖苞。在這種場景中拯欧,需要一個(gè)類使用無主類型,而另一個(gè)類使用隱式解析可選屬性财骨。

這個(gè)兩個(gè)屬性在初始化完成后能被直接訪問 (不需要展開) 镐作,同時(shí)避免了引用循環(huán)藏姐。

例如:
定義兩個(gè)類, CountryCity该贾,每個(gè)類都將另一個(gè)類的實(shí)例保存為屬性羔杨。在這個(gè)模型中,每個(gè)國家都必須有首都杨蛋,每個(gè)城市都必須屬于一個(gè)國家兜材。為了實(shí)現(xiàn)這種關(guān)系, Country 類擁有一個(gè) capitalCity 屬性逞力,而 City 類擁有一個(gè) country 屬性:

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
    
    deinit {
        print("city \(name) is being deinitialized")
    }
}

class City {
    let name: String
    let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
    deinit {
        print("county: \(name) is being deinitialized")
    }
}

var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa”

為了建立兩個(gè)類的依賴關(guān)系护姆, City 的構(gòu)造含函數(shù)接受一個(gè)Country 實(shí)例作為參數(shù),并且將實(shí)例保存到 country 屬性掏击。

Country 的構(gòu)造函數(shù)調(diào)用了 City 的構(gòu)造函數(shù)卵皂。然而,只有 Country 的實(shí)例完全初始化后砚亭, Country 的構(gòu)造函數(shù)才能把 self 傳給 City 的構(gòu)造函數(shù)灯变。 (在兩段式構(gòu)造過程中有具體描述。)

// 將 capitalCity 屬性改為非隱式可選類型的話
class Country {
    let name: String
    var capitalCity: City
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
    deinit {
        print("city \(name) is being deinitialized")
    }
}

// 編譯器報(bào)錯(cuò)
//'self' used before all stored properties are initialized
// self.capitalCity = City(name: capitalName, country: self) 此時(shí) self 沒有完成初始化

為了滿足這種需求捅膘,通過在類型結(jié)尾處加上感嘆號(hào) City! 的方式添祸,將 CountrycapitalCity 屬性聲明為隱式解析可選類型的屬性。這意味像其他可選類型一樣寻仗, capitalCity屬性的默認(rèn)值為 nil刃泌,單是不需要展開它的值就能訪問它。(在隱式解析可選類型中有描述署尤。)

由于 capitalCity 默認(rèn)值為 nil耙替,一旦 Country 的實(shí)例在構(gòu)造函數(shù)中給 name 屬性賦值后,整個(gè)初始化過程就完成了曹体。這意味著一旦 name 屬性被賦值后俗扇, Country 的構(gòu)造函數(shù)就能引用并傳遞隱式的 selfCountry 的構(gòu)造函數(shù)在賦值 capitalCity 時(shí)就能將 self 作為參數(shù)傳遞給 City 的構(gòu)造函數(shù)箕别。

// 如果不先初始化 name 則會(huì)報(bào)錯(cuò)
class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.capitalCity = City(name: capitalName, country: self)
        self.name = name
    }
    deinit {
        print("city \(name) is being deinitialized")
    }
}
//error: 'self' used before all stored properties are initialized
//由于沒有完成初始化铜幽,不能講self 作為參數(shù)傳遞給 City的構(gòu)造函數(shù) self.capitalCity = City(name: capitalName, country: self)

閉包引起的強(qiáng)引用循環(huán)

強(qiáng)引用循環(huán)還會(huì)發(fā)生在當(dāng)你講一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性,并且這個(gè)閉包中又捕獲(使用)了這個(gè)類實(shí)例時(shí)串稀。這個(gè)閉包中可能訪問了實(shí)例的某個(gè)屬性除抛,例如 self.somProperty ,或者閉包中調(diào)用了實(shí)例的某個(gè)方法母截,例如 self.someMethod() 到忽。這兩種情況都導(dǎo)致了閉包捕獲 self ,從而產(chǎn)生了強(qiáng)引用循環(huán)微酬。

強(qiáng)引用循環(huán)的產(chǎn)生绘趋,是因?yàn)殚]包和類相似,都是引用類型颗管。當(dāng)你把一個(gè)閉包賦值給某個(gè)屬性時(shí)陷遮,你是講這個(gè)閉包的引用賦值給了屬性。實(shí)質(zhì)上垦江,這和之前的問題是一樣的 -- 兩個(gè)強(qiáng)引用彼此一直有效帽馋。但是,和兩個(gè)類實(shí)例不同比吭,這次是一個(gè)實(shí)例绽族,另一個(gè)是閉包。

閉包引起的強(qiáng)引用循環(huán)示例:

class HTMLElement {
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)/>"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil
// 無輸出

實(shí)例的 paragraphasHTML 屬性持有閉包的強(qiáng)引用衩藤,但是吧慢,閉包在其內(nèi)部引用了 self (引用了 self.nameself.text),因此閉包捕獲了 self赏表,這意味著閉包反過來持有了 paragraph 的強(qiáng)引用检诗。 這樣兩個(gè)對象就產(chǎn)生了強(qiáng)引用循環(huán)。

注意:
雖然閉包多次使用了 self 瓢剿,它只捕獲 HTMLElement 實(shí)例的一個(gè)強(qiáng)引用逢慌。

如果設(shè)置 paragraph 變量為 nil, 打破它持有的 HTMLElement 實(shí)例的強(qiáng)引用间狂, 由于強(qiáng)引用循環(huán)攻泼,HTMLElement 實(shí)例和它的閉包都不會(huì)銷毀。

解決閉包引起的強(qiáng)引用循環(huán)

Swift 提供了一種優(yōu)雅的方式來解決閉包引起的強(qiáng)引用循環(huán)問題鉴象,稱為 閉包捕獲列表 (closure capture list)

在定義閉包的同時(shí)忙菠,定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實(shí)例之間的強(qiáng)引用循環(huán)纺弊。捕獲列表定義了閉包體內(nèi)捕獲一個(gè)或者多個(gè)引用類型的規(guī)則只搁。和類實(shí)例之間的強(qiáng)引用循環(huán)一樣,聲明每個(gè)捕獲的引用為弱引用或無主引用來替代強(qiáng)引用俭尖。根據(jù)代碼關(guān)系來決定使用弱引用還是無主引用氢惋。

注意
Swift 要求只要在閉包內(nèi)使用了 self 的成員,就要用 self.someProperty 或者 self.someMethod() (而不只是 somePropertysomeMethod())稽犁。這提醒你可能一不小心就捕獲了 self 焰望。

定義捕獲列表

駁貨列表的每一項(xiàng)都由一對元素組成,一個(gè)元素是 weakunowned 關(guān)鍵字已亥,另一個(gè)元素是類實(shí)例的引用(如 self) 或初始化過的變量 (如 delegate = self.delegate!)熊赖。這些項(xiàng)在方括號(hào)中用逗號(hào)分開。

如果閉包有參數(shù)列表和返回類型虑椎,把捕獲列表放在它們前面:

lazy var someClousure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProress: String) -> String in
    // 這里是閉包的函數(shù)體
}

如果閉包沒有知名參數(shù)或返回類型震鹉,即它們會(huì)通過上下文推斷俱笛,那么可以把捕獲列表和關(guān)鍵字 in 放在閉包最開始的地方:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in // 這里是閉包的函數(shù)體
}

弱引用和無主引用

在閉包和捕獲的實(shí)例總是相互引用并且總是同時(shí)銷毀時(shí),將閉包內(nèi)的捕獲值定義為 無主引用

相反的传趾,在被捕獲的引用可能會(huì)變成 nil 時(shí)迎膜,將閉包內(nèi)的捕獲定義為 弱引用。 弱引用總是可選類型浆兰,并且當(dāng)引用的實(shí)例被銷毀后磕仅,弱引用的值會(huì)自動(dòng)置為 nil。這使我們可以在閉包內(nèi)檢查它們是否存在簸呈。

注意
如果被捕獲的引用絕對不會(huì)變?yōu)?nil榕订,應(yīng)該用無主引用,而不是弱引用蜕便。

class HTMLElement {
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)/>"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil

//p is being deinitialized

asHTML 閉包中通過添加捕獲列表 [unowned self] 來表示將 self 捕獲為無主引用而不是強(qiáng)引用劫恒,這樣閉包以無主引用的方式捕獲 self,并不會(huì)持有 HTMLElement 實(shí)例的強(qiáng)引用轿腺。 將 paragraph 置為 nil 后兼贸, HTMLElement 實(shí)例將會(huì)被銷毀。

AnyAnyObject 的類型轉(zhuǎn)換

Swift 為不確定類型提供了兩種特殊的類型別名:

  • Any 可以表示任何類型吃溅,包括函數(shù)類型溶诞。
  • AnyObject 可以表示任何類類型的實(shí)例。

重載運(yùn)算符使用static修飾:

Swift提供了類似于C++中的重載運(yùn)算符方法决侈,可以對自定義類型實(shí)現(xiàn)運(yùn)算符操作螺垢。
在定義的時(shí)候,運(yùn)算符函數(shù)都是static修飾的赖歌,因?yàn)檫\(yùn)算符函數(shù)是類方法(或者結(jié)構(gòu)枉圃,枚舉)。

運(yùn)算符

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庐冯,一起剝皮案震驚了整個(gè)濱河市孽亲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展父,老刑警劉巖返劲,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異栖茉,居然都是意外死亡篮绿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門吕漂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亲配,“玉大人,你說我怎么就攤上這事『鸹ⅲ” “怎么了犬钢?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長思灰。 經(jīng)常有香客問我玷犹,道長,這世上最難降的妖魔是什么官辈? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮遍坟,結(jié)果婚禮上拳亿,老公的妹妹穿的比我還像新娘。我一直安慰自己愿伴,他們只是感情好肺魁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著隔节,像睡著了一般鹅经。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怎诫,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天瘾晃,我揣著相機(jī)與錄音,去河邊找鬼幻妓。 笑死蹦误,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肉津。 我是一名探鬼主播强胰,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妹沙!你這毒婦竟也來了偶洋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤距糖,失蹤者是張志新(化名)和其女友劉穎玄窝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悍引,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哆料,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吗铐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东亦。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出典阵,到底是詐尸還是另有隱情奋渔,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布壮啊,位于F島的核電站嫉鲸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏歹啼。R本人自食惡果不足惜玄渗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狸眼。 院中可真熱鬧藤树,春花似錦、人聲如沸拓萌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽微王。三九已至屡限,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炕倘,已是汗流浹背钧大。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罩旋,地道東北人拓型。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像瘸恼,于是被迫代替她去往敵國和親劣挫。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348