Swift 学习总结七: 可选链、自动引用计数

本文详细介绍了Swift中的可选链技术,包括如何使用可选链访问属性、调用方法和下标脚本,以及如何处理可选类型。同时,深入探讨了Swift的自动引用计数(ARC)机制,如何解决类实例之间的循环强引用问题,以及闭包引起的循环强引用。

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
潮汐研究作为海洋科学的关键分支,融合了物理海洋学、地理信息系统及水利工程等多领域知识。TMD2.05.zip是一套基于MATLAB环境开发的潮汐专用分析工具集,为科研人员与工程实践者提供系统化的潮汐建模与计算支持。该工具箱通过模块化设计实现了两大核心功能: 在交互界面设计方面,工具箱构建了图形化操作环境,有效降低了非专业用户的操作门槛。通过预设参数输入模块(涵盖地理坐标、时间序列、测站数据等),用户可自主配置模型运行条件。界面集成数据加载、参数调整、可视化呈现及流程控制等标准化组件,将复杂的数值运算过程转化为可交互的操作流程。 在潮汐预测模块中,工具箱整合了谐波分解法与潮流要素解析法等数学模型。这些算法能够解构潮汐观测数据,识别关键影响要素(包括K1、O1、M2等核心分潮),并生成不同时间尺度的潮汐预报。基于这些模型,研究者可精准推算特定海域的潮位变化周期与振幅特征,为海洋工程建设、港湾规划设计及海洋生态研究提供定量依据。 该工具集在实践中的应用方向包括: - **潮汐动力解析**:通过多站点观测数据比对,揭示区域主导潮汐成分的时空分布规律 - **数值模型构建**:基于历史观测序列建立潮汐动力学模型,实现潮汐现象的数字化重构与预测 - **工程影响量化**:在海岸开发项目中评估人工构筑物对自然潮汐节律的扰动效应 - **极端事件模拟**:建立风暴潮与天文潮耦合模型,提升海洋灾害预警的时空精度 工具箱以"TMD"为主程序包,内含完整的函数库与示例脚本。用户部署后可通过MATLAB平台调用相关模块,参照技术文档完成全流程操作。这套工具集将专业计算能力与人性化操作界面有机结合,形成了从数据输入到成果输出的完整研究链条,显著提升了潮汐研究的工程适用性与科研效率。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值