From: AceVest Date: Sun, 19 Mar 2017 09:28:36 +0000 (+0800) Subject: Swift - Deinitialization ARC OptionalChaining X-Git-Url: http://zhaoyanbai.com/repos/Bv9ARM.html?a=commitdiff_plain;h=d694c17c535a2a690491f2547f39c0d44d5df9e1;p=acecode.git Swift - Deinitialization ARC OptionalChaining --- diff --git a/learn/AcePlay/AcePlay.playground/Pages/ARC.xcplaygroundpage/Contents.swift b/learn/AcePlay/AcePlay.playground/Pages/ARC.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..5f157bb --- /dev/null +++ b/learn/AcePlay/AcePlay.playground/Pages/ARC.xcplaygroundpage/Contents.swift @@ -0,0 +1,323 @@ +//: [Previous](@previous) + +import Foundation + +var str = "Hello, Automatic Reference Counting" + +//: [Next](@next) + + +// 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递 + + +class Apartment { + var address: String + init(address: String) { + self.address = address + } + + var tenant: Person? + + deinit { + print("Apartment \(self.address) is being deinitialized") + } +} + +class Person { + var name: String + + init(name: String) { + if name.isEmpty { + self.name = "[Unnamed]" + } else { + self.name = name + } + + print("Person \(name) is being initialized") + } + + + deinit { + print("Person \(name) is being deinitialized") + } + + var apartment: Apartment? +} + + +var reference1: Person? +var reference2: Person? +var reference3: Person? + + +reference1 = Person(name: "Ace") +reference2 = reference1 +reference3 = reference1 + +reference1 = nil +reference2 = nil + +// 现在这个Person实例还不会被销毁 + +print("----------destroy reference3------------") +reference3 = nil + + + +// 类间的循环强引用 +var abel: Person? +var unit4A: Apartment? + +abel = Person(name: "abel") +unit4A = Apartment(address: "Apartment 4A") + +abel!.apartment = unit4A +unit4A!.tenant = abel + + +// 解决实例之间的循环强引用 +// 弱引用( weak reference )和无主引用( unowned reference ) +// 1. 对于生命周期中会变为 nil 的实例使用弱引用 +// 2. 对于初始化赋值后再也不会被赋值为 nil 的实例,使用无主引用 + + +// 弱引用 +// 场景1: 一个人可以没有公寓,一个公寓也可以没有租客 +class PersonWeak { + var name: String + + init(name: String) { + if name.isEmpty { + self.name = "[Unnamed]" + } else { + self.name = name + } + + print("PersonWeak \(name) is being initialized") + } + + + deinit { + print("PersonWeak \(name) is being deinitialized") + } + + var apartment: ApartmentWeak? +} + +class ApartmentWeak { + var address: String + init(address: String) { + self.address = address + } + + + // 由于弱引用需要允许它们的值为 nil ,它们一定得是可选类型 + weak var tenant: PersonWeak? + + deinit { + print("ApartmentWeak \(self.address) is being deinitialized") + } +} + + +// 这意味着当PersonWeak bill的强引用被设置为nil时, ApartmentWeak对PersonWeak的弱引用tenant自动设置为nil +var bill: PersonWeak? +var unit7C: ApartmentWeak? + +bill = PersonWeak(name: "Bill") +unit7C = ApartmentWeak(address: "ApartmentWeak 7C") +bill!.apartment = unit7C +unit7C!.tenant = bill +print("----------destroy bill------------") +// PersonWeak实例依然保持对ApartmentWeak实例的强引用 +// 但是ApartmentWeak实例现在对PersonWeak实例是弱引用 +// 这意味着当你断开bill变量所保持的强引用时,再也没有指向PersonWeak实例的强引用了,由于再也没有指向PersonWeak实例的强引用,该实例会被释放 +bill = nil + + +print("----------destroy unit7C------------") +// 现在只剩下来自unit7C变量对ApartmentWeak实例的强引用。如果你打断这个强引用,那么ApartmentWeak实例就再也没有强引用了 +unit7C = nil + + + +// 无主引用 +// 无主引用假定是永远有值的。因此,无主引用总是被定义为非可选类型 +// 场景2: 一个用户可以没有信用卡,一张信用卡必须有持有人 +class Customer { + let name: String + var card: CreditCard? + + init(name: String) { + self.name = name + } + + deinit { + print("\(name) is being uninitialized") + } +} + +class CreditCard { + let number: UInt64 + unowned let customer: Customer + + init(number: UInt64, customer: Customer) { + self.number = number + self.customer = customer + } + + deinit { + print("Card #\(self.number) is being uninitialized") + } +} + + + +var carl: Customer? +carl = Customer(name: "Carl") +carl!.card = CreditCard(number: 314_1414_1732, customer: carl!) + +// 现在Customer实例对CreditCard实例有一个强引用,并且CreditCard实例对Customer实例有一个无主引用 +// 由于 Customer 的无主引用,当你断开 john 变量持有的强引用时,那么就再也没有指向 Customer 实例的强引用了 +// 因为不再有 Customer 的强引用,该实例被释放了。其后,再也没有指向 CreditCard 实例的强引用,该实例也随之被释放了 +print("----------destroy carl------------") +carl = nil + + + + + + +// 无主引用和隐式展开的可选属性 +// 除了两个属性的值都允许为nil和一个属性的值允许为nil,而另一个属性的值不允许为nil的场景 +// 还有第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式展开的可选属性 + +// 场景三: 一个国家必定有首都城市,一个城市必定属于一个国家 +class Country { + let name: String + var capital: City! + + init(name: String, capitalName: String) { + self.name = name + self.capital = City(name: capitalName, country: self) + } + + deinit { + print("Country: \(name) is being uninitialized") + } +} + + +class City { + let name: String + unowned let country: Country + + init(name: String, country: Country) { + self.name = name + self.country = country + } + + deinit { + print("City: \(name) is being uninitialized") + } +} + +var country: Country? = Country(name: "Canada", capitalName: "Ottwa") +print("\(country!.name)'s capital city is called \(country!.capital.name)") + +print("----------destroy country------------") +country = nil + + + + + +// 闭包的循环强引用 + +class HTMLElement { + + let name: String + let text: String? + + lazy var asHTML: () -> String = { + if let text = self.text { + return "<\(self.name)>\(text)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + print("HTMLElement: \(name) is being deinitialized") + } + +} + +var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") +print(paragraph!.asHTML()) + +// 此时就处把paragraph设置为nil,paragraph曾指向的实例和闭包之间还是强引用 +// HTMLElement 实例和它的闭包都不会被释放 +paragraph = nil + + +// Swift通过闭包捕获列表来解决这个问题 +// Swift要求你在闭包中引用self成员时使用self.someProperty或者self.someMethod 而不只是 someProperty或someMethod)。这有助于提醒你可能会一不小心就捕获了self + +// 捕获列表中的每一项都由 weak 或 unowned 关键字与类实例的引用(如 self )或初始化过的变量(如 delegate = self.delegate! )成对组成。这些项写在方括号中用逗号分开。 +// 把捕获列表放在形式参数和返回类型前边,如果它们存在的话 + + +//lazy var someClosure: (Int, String) -> String = { +// [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in +// // closure body goes here +//} + + +// 如果闭包没有指明形式参数列表或者返回类型,是因为它们会通过上下文推断,那么就把捕获列表放在关键字 in 前边,闭包最开始的地方 +//lazy var someClosure: Void -> String = { +// [unowned self, weak delegate = self.delegate!] in +// // closure body goes here +//} + + + +// 弱引用和无主引用 +// 在闭包和捕获的实例总是互相引用并且总是同时释放时,将闭包内的捕获定义为无主引用 +// 在被捕获的引用可能会变为 nil 时,定义一个弱引用的捕获。弱引用总是可选项,当实例的引用释放时会自动变为 nil 。这使我们可以在闭包体内检查它们是否存在 +// 如果被捕获的引用永远不会变为 nil ,应该用无主引用而不是弱引用 + +class HTMLElementV2 { + + let name: String + let text: String? + + lazy var asHTML: () -> String = { + [unowned self] in + if let text = self.text { + return "<\(self.name)>\(text)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + print("HTMLElementV2: \(name) is being deinitialized") + } +} + +print("----------destroy paragraph2------------") +var paragraph2: HTMLElementV2? = HTMLElementV2(name: "p", text: "hello ARC") +paragraph2 = nil + +print("begin deinit") \ No newline at end of file diff --git a/learn/AcePlay/AcePlay.playground/Pages/Deinitialization.xcplaygroundpage/Contents.swift b/learn/AcePlay/AcePlay.playground/Pages/Deinitialization.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..f0e828a --- /dev/null +++ b/learn/AcePlay/AcePlay.playground/Pages/Deinitialization.xcplaygroundpage/Contents.swift @@ -0,0 +1,57 @@ +//: [Previous](@previous) + +import Foundation + +var str = "Hello, Deinitialization" + +//: [Next](@next) + +// 析构器只适用于类类型 + +// 子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用 + +class Bank { + static var totalCoins = 10000 + + static func distribute(coins numberOfCoinsRequested: Int) -> Int { + let numberOfCoinsToVend = min(totalCoins, numberOfCoinsRequested) + + totalCoins -= numberOfCoinsToVend + + return numberOfCoinsToVend + } + + static func receive(coins: Int) { + totalCoins += coins + } +} + + +class Player { + var coinsInPurse: Int + + init(coins: Int) { + coinsInPurse = Bank.distribute(coins: coins) + } + + func win(coins: Int) { + coinsInPurse += Bank.distribute(coins: coins) + } + + deinit { + Bank.receive(coins: coinsInPurse) + } +} + + +var player: Player? = Player(coins: 100) +print("A player has joined the game with \(player!.coinsInPurse) coins") +print("There are now \(Bank.totalCoins) coins left in the bank") + +player?.win(coins: 1000) +print("A player won 1000 coins and now has \(player!.coinsInPurse) coins") +print("There are now \(Bank.totalCoins) coins left in the bank") + +player = nil +print("the player has left the game") +print("There are now \(Bank.totalCoins) coins left in the bank") diff --git a/learn/AcePlay/AcePlay.playground/Pages/OptionalChaining.xcplaygroundpage/Contents.swift b/learn/AcePlay/AcePlay.playground/Pages/OptionalChaining.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..0705036 --- /dev/null +++ b/learn/AcePlay/AcePlay.playground/Pages/OptionalChaining.xcplaygroundpage/Contents.swift @@ -0,0 +1,227 @@ +//: [Previous](@previous) + +import Foundation + +var str = "Hello, Optional Chaining" + +//: [Next](@next) + + +// 使用可选链式调用代替强制展开 +// 通过在想调用的属性、方法、或下标的可选值后面放一个问号(?),可以定义一个可选链。这一点很像在可选值后面放一个叹号(!)来强制展开它的值。 +// 它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。 + +// 特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。 +// 例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是Int类型,则会变为Int?类型 + +class PersonV1 { + var residence: RecidenceV1? +} + +class RecidenceV1 { + var numberOfRooms = 7 +} + +let john = PersonV1() + +// 当john.residence==nil时以下这句会引发运行错误 +// let roomCount == john.residence!.numberOfRooms + +if let roomCount = john.residence?.numberOfRooms { + print("John's residence has \(roomCount) room(s)") +} else { + print("Unable to retrieve the number of rooms.") +} + + + + +john.residence = RecidenceV1() +if let roomCount = john.residence?.numberOfRooms { + print("John's residence has \(roomCount) room(s)") +} else { + print("Unable to retrieve the number of rooms.") +} + + +// 通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。 +class Person { + var residence: Recidence? +} + +class Recidence { + var rooms = [Room]() + var numberOfRooms: Int { + return rooms.count + } + var address: Address? + + subscript(i: Int) -> Room { + get { + return rooms[i] + } + set { + rooms[i] = newValue + } + } + + func printNumberOfRooms() { + print("The number of room is \(numberOfRooms)") + } + +} + +class Room { + let name: String + init(name: String) { + self.name = name + } +} + +class Address { + var buildingName: String? + var buildingNumber: String? + var street: String? + + func buildingIdentifier() -> String? { + if buildingName != nil { + return buildingName + } else if buildingNumber != nil && street != nil { + return "\(buildingNumber!) \(street!)" + } else { + return nil + } + } +} + +let obama = Person() +if let roomCount = obama.residence?.numberOfRooms { + print("Obama's residence has \(roomCount) room(s)") +} else { + print("Unable to retrieve the number of rooms.") +} + + +let addr = Address() +addr.buildingNumber = "31" +addr.street = "Wall Street" + +obama.residence?.address = addr // 此时residence==nil,会设置失败 +// 上面的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行 + + +func CreateAddress() -> Address { + print("CreateAddress was Called") + let addr = Address() + addr.buildingNumber = "13" + addr.street = "Pennsylvania Avenue" + return addr +} + +obama.residence?.address = CreateAddress() // 可以通过并未输出print内容来验证出CreateAddress并未被执行 + + +// 对于Residence.printNumberOfRooms +// 如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是Void?,而不是Void,因为通过可选链式调用得到的返回值都是可选的 + +if obama.residence?.printNumberOfRooms() == nil { + print("It was not possible to print the number of rooms.") +} else { + print("It was possible to print the number of rooms.") +} + + +// 同样的,可以据此判断通过可选链式调用为属性赋值是否成功 +// 即使residence为nil。通过可选链式调用给属性赋值会返回Void?,通过判断返回值是否为nil就可以知道赋值是否成功 +if (obama.residence?.address = addr) == nil { + print("It was not possible to set the address.") +} else { + print("It was possible to set the address.") +} + + + + +// 通过可选链式调用访问下标 +// 注意: 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。 +if let firstRoomName = obama.residence?[0].name { + print("The first room name is \(firstRoomName).") +} else { + print("Unable to retrieve the first room name.") +} + + +print("It's time to buy a house for Obama") +let obamaHouse = Recidence() +obamaHouse.rooms.append(Room(name: "Kitchen")) +obamaHouse.rooms.append(Room(name: "Living Room")) + +obama.residence = obamaHouse + +if let firstRoomName = obama.residence?[0].name { + print("The first room name is \(firstRoomName).") +} else { + print("Unable to retrieve the first room name.") +} + + + + +// 访问可选类型的下标 +var scores = ["Trump": [59, 48,66], "Obama": [44, 65, 73], "Bush": [23, 32, 45], "Clinton": [37, 76, 19]] +scores["Trump"]?[0] = 58 +scores["Bush"]?[2] += 4 +// scores["Clinton"]?[5] = 33 // error 因为,可选链会返回一个数组,然后索引为5就超出了范围 +scores["Ace"]?[1] = 100 // 调用失败,但不会出错 +print(scores.description) + + + +// 连接多层可选链式调用 +// 可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。 +// 也就是说 +// 1. 如果你访问的值不是可选的,可选链式调用将会返回可选值 +// 2. 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选” + +// 因此 +// 通过可选链式调用访问一个Int值,将会返回Int?,无论使用了多少层可选链式调用 +// 通过可选链式调用访问Int?值,依旧会返回Int?值,并不会返回Int?? + + +if let obamasStreet = obama.residence?.address?.street { + print("Obama's street name is \(obamasStreet)") +} else { + print("Unable to retrieve the address") +} + +print("It's time to give address to Obama's House") +obama.residence?.address = CreateAddress() +if let obamasStreet = obama.residence?.address?.street { + print("Obama's street name is \(obamasStreet)") +} else { + print("Unable to retrieve the address") +} + + + +// 在方法的可选返回值上进行可选链式调用 +// 上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值 +// 还可以在一个可选值上通过可选链式调用来调用方法 +// 并且可以根据需要继续在方法的可选返回值上进行可选链式调用 + +if let buildingIdentifier = obama.residence?.address?.buildingIdentifier() { + print("Obama's building identifier is \(buildingIdentifier)") +} + + +// 还可以在该方法的返回值上进行链式调用 + +if let beginWithThe = obama.residence?.address?.buildingIdentifier()?.hasPrefix("The") { + if beginWithThe { + print("Obama's building identifier begins with \"The\"") + } else { + print("Obama's building identifier does not begins with \"The\"") + } +} + +print("End") diff --git a/learn/AcePlay/AcePlay.playground/Sources/Utils.swift b/learn/AcePlay/AcePlay.playground/Sources/Utils.swift index 17f7185..0c6c20d 100644 --- a/learn/AcePlay/AcePlay.playground/Sources/Utils.swift +++ b/learn/AcePlay/AcePlay.playground/Sources/Utils.swift @@ -5,3 +5,4 @@ func printLine(_ title: String) -> Void { let line = String(format:"-----------------------------------<%@>", title) print(line) } + diff --git a/learn/AcePlay/AcePlay.playground/contents.xcplayground b/learn/AcePlay/AcePlay.playground/contents.xcplayground index 17e0832..bc794cb 100644 --- a/learn/AcePlay/AcePlay.playground/contents.xcplayground +++ b/learn/AcePlay/AcePlay.playground/contents.xcplayground @@ -13,5 +13,8 @@ + + + \ No newline at end of file diff --git a/learn/AcePlay/AcePlay.playground/playground.xcworkspace/xcuserdata/Ace.xcuserdatad/UserInterfaceState.xcuserstate b/learn/AcePlay/AcePlay.playground/playground.xcworkspace/xcuserdata/Ace.xcuserdatad/UserInterfaceState.xcuserstate index 1389b40..c4b2fc5 100644 Binary files a/learn/AcePlay/AcePlay.playground/playground.xcworkspace/xcuserdata/Ace.xcuserdatad/UserInterfaceState.xcuserstate and b/learn/AcePlay/AcePlay.playground/playground.xcworkspace/xcuserdata/Ace.xcuserdatad/UserInterfaceState.xcuserstate differ