Swift 可选链

可选链(Optional Chaining)是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。

可选链返回两个值:

  • 如果目标有值,调用就会成功,返回该值
  • 如果目标为nil,调用将返回nil

多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效。

可选链可替代强制解析

通过在属性、方法、或下标脚本的可选值后面放一个问号(?),即可定义一个可选链。

可选链 ‘?’

感叹号(!)强制展开方法,属性,下标脚本可选链

? 放置于可选值后来调用方法,属性,下标脚本

! 放置于可选值后来调用方法,属性,下标脚本来强制展开值

当可选为 nil 输出比较友好的错误信息

当可选为 nil 时强制展开执行错误

为可选链定义模型类

你可以使用可选链来多层调用属性,方法,和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

通过可选链调用方法

你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。

使用可选链调用下标脚本

你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功,然而,你不能通过可选链来设置下标脚本。

通过可选链接调用来访问下标

通过可选链接调用,我们可以用下标来对可选值进行读取或写入,并且判断下标调用是否成功

class Person {
    var residence: Residence?
}

class Residence {
    var roms = [Room]()
    var numberOfRooms: Int{
        return self.roms.count
    }
    subscript(index: Int) -> Room{
        return roms[index]
    }
    func printNumberOfRooms() {
        print("房间号为\(numberOfRooms)")
    }
    var address: Address?
}

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{
            return buildingNumber
        }else{
            return nil
        }
        
    }
}


        let john = Person()
        let johnsHouse = Residence()
        johnsHouse.roms.append(Room(name: "客厅"))
        johnsHouse.roms.append(Room(name: "厨房"))
        john.residence = johnsHouse

        let johnsAddress = Address()
        johnsAddress.buildingName = "The Larches"
        johnsAddress.street = "Laurel Street"
        john.residence!.address = johnsAddress

        //可选链访问属性
        if let johnsStreet = john.residence?.address?.street {
            print("John 所在的街道是 \(johnsStreet)。")
        } else {
            print("无法检索到地址。 ")
        }
        //访问方法
        john.residence?.printNumberOfRooms()
        //访问下标脚本
        if let room = john.residence?[0] {
            print(room.name)
        }
         //        let rom = Room(name: "haha")
        //        john.residence?[0] = rom  //只读报错
        
        //通过可选链接调用,我们可以用下标来对可选值进行读取或写入
        john.residence?[0].name = "健身房"
        if let roomName = john.residence?[0].name {
            print(roomName)
        }

执行结果:

John 所在的街道是 Laurel Street。
房间号为2
客厅
健身房
访问可选类型的下标

如果下标返回可空类型值,比如Swift中Dictionary的key下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

Swift 自动引用计数(ARC)

Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存

通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。

但在有些时候我们还是需要在代码中实现内存管理。

ARC 功能
  • 当每次使用 init() 方法创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。
  • 内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
  • 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。
  • 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。
  • 实例赋值给属性、常量或变量,它们都会创建此实例的强引用,只要强引用还在,实例是不允许被销毁的
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 开始初始化")
    }
    deinit {
        print("\(name) 被析构")
    }
}

// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 创建Person类的新实例
reference1 = Person(name: "Runoob")


//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1

//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil

以上程序执行输出结果为:

Runoob 开始初始化
Runoob 被析构
类实例之间的循环强引用

在上面的例子中,ARC 会跟踪你所新创建的 Person 实例的引用数量,并且会在 Person 实例不再被需要时销毁它。

然而,我们可能会写出这样的代码,一个类永远不会有0个强引用。这种情况发生在两个类实例互相保持对方的强引用,并让对方不被销毁。这就是所谓的循环强引用。

实例

下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:Person和Apartment,用来建模公寓和它其中的居民:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var runoob: Person?
var number73: Apartment?

// 赋值
runoob = Person(name: "Runoob")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 runoob 和 number73 中的实例
// 循环强引用被创建
runoob!.apartment = number73
number73!.tenant = runoob

// 断开 runoob 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
runoob = nil
number73 = nil
解决实例之间的循环强引用

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:

  • 弱引用
  • 无主引用

弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。

对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。

弱引用实例
class Module {
    let name: String
    init(name: String) {
        self.name = name
    }
    var subModule: SubModule?
    deinit {
        print("\(name)模块deinit")
    }
    
}

class SubModule {
    let number: Int
    init(number: Int) {
        self.number = number
    }
    //对于生命周期中会变为nil的实例使用弱引用(有问号)
    weak var module: Module?
    deinit {
        print("SubModule number\(number) deinit")
    }
}

        var module: Module? = Module(name: "Module")
        var subModule: SubModule? = SubModule(number: 8)
        module!.subModule = subModule
        subModule!.module = module
        module = nil
        subModule = nil

执行结果:

Module模块deinit
SubModule number8 deinit
无主引用实例
class ModuleTwo {
    let name: String
    init(name: String) {
        self.name = name
    }
    var subModuleTwo: SubModuleTwo?
    deinit {
        print("\(name)模块deinit")
    }
    
}
class SubModuleTwo {
    let name: String
    //对于初始化赋值后再也不会被赋值为nil的实例(没有问号),使用无主引用
    unowned var module: ModuleTwo
    init(name: String, module: ModuleTwo) {
        self.name = name
        self.module = module
    }
    deinit {
        print("\(name)模块 deinit")
    }
}

        var module2: ModuleTwo? = ModuleTwo(name: "ModuleTwo")
        module2?.subModuleTwo = SubModuleTwo(name: "SubModuleTwo", module: module2!)
        module2 = nil;

运行结果:

ModuleTwo模块deinit
SubModuleTwo模块 deinit
闭包引起的循环强引用

循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包 “捕获” self,从而产生了循环强引用

弱引用和无主引用
  • 当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
  • 相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
  • 如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用。
class HtmlElement {
    let name: String
    let text: String
    init(name: String, text: String) {
        self.name = name
        self.text = text
    }
    lazy var OutPutClosure: () -> String = {
        [unowned self] in
        return "<\(self.name)>\(self.text)</\(self.name)>"
    }
    deinit {
        print("HtmlElement deinit")
    }
}

        var hemlC: HtmlElement? = HtmlElement(name: "div", text: "hello world")
        print(hemlC!.OutPutClosure())
        print(hemlC!.OutPutClosure())
        hemlC = nil

执行结果:

<div>hello world</div>
<div>hello world</div>
HtmlElement deinit