Swift 内存管理,WEAK 和 UNOWNED

Swift之深入解析内存管理的底层原理 一、Swift 内存管理 ① ARC 跟 OC 一样,Swift 也是采用基于引用计数的 ARC 内存管理方案(针对堆空间); Swift 的 ARC 中有三种引用: 强引用(strong reference):默认情况下,引用都是强引用; 弱引用(weak reference):通过 weak 定义弱引用; 必须是可选类型的 var,因为实例销毁后,ARC 会自动将弱引用设置为 nil; ARC 自动给弱引用设置 nil 时,不会触发属性观察器; 无主引用(unowned reference): 阅读详情

不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。

Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等),就可以确保内存使用不出现问题。

但是,所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。

什么是循环引用

虽然我觉得循环引用这样的概念介绍不太应该出现在这本书中,但是为了更清晰地解释 Swift 中的循环引用的一般情况,这里还是简单进行说明。假设我们有两个类 A 和 B, 它们之中分别有一个存储属性持有对方:

class A {
    let b: B
    init() {
        b = B()
        b.a = self
    }

    deinit {
        print("A deinit")
    }
}

class B {
    var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A,然后即使立即将其释放,A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。

var obj: A? = A()
obj = nil
// 内存没有释放

因为即使 obj 不再持有 A 的这个对象,b 中的 b.a 依然引用着这个对象,导致它无法释放。而进一步,a 中也持有着 b,导致 b 也无法释放。在将 obj 设为 nil 之后,我们在代码里再也拿不到对于这个对象的引用了,所以除非是杀掉整个进程,我们已经永远也无法将它释放了。多么悲伤的故事啊..

在 Swift 里防止循环引用

为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 “被动” 的一方不要去持有 “主动” 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:

class B {
    weak var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 var a 前面加上了 weak,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:

A deinit
B deinit

可能有心的朋友已经注意到,在 Swift 中除了 weak 以外,还有另一个冲着编译器叫喊着类似的 “不要引用我” 的标识符,那就是 unowned。它们的区别在哪里呢?如果您是一直写 Objective-C 过来的,那么从表面的行为上来说 unowned 更像以前的 unsafe_unretained,而 weak 就是以前的 weak。用通俗的话说,就是 unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 “无效的” 引用,它不能是 Optional 值,也不会被指向 nil。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 则友好一些,在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (因此被标记为 @weak 的变量一定需要是 Optional 值)。关于两者使用的选择,Apple 给我们的建议是如果能够确定在访问时不会已被释放的话,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak。

我们结合实际编码中的使用来看看选择吧。日常工作中一般使用弱引用的最常见的场景有两个:

设置 delegate 时
在 self 属性存储为闭包时,其中拥有对 self 引用时
前者是 Cocoa 框架的常见设计模式,比如我们有一个负责网络请求的类,它实现了发送请求以及接收请求结果的任务,其中这个结果是通过实现请求类的 protocol 的方式来实现的,这种时候我们一般设置 delegate 为 weak:

// RequestManager.swift
class RequestManager: RequestHandler {

    @objc func requestFinished() {
        print("请求完成")
    }

    func sendRequest() {
        let req = Request()
        req.delegate = self

        req.send()
    }
}

// Request.swift
@objc protocol RequestHandler {
    optional func requestFinished()
}

class Request {
    weak var delegate: RequestHandler!;

    func send() {
        // 发送请求
        // 一般来说会将 req 的引用传递给网络框架
    }

    func gotResponse() {
        // 请求返回
        delegate?.requestFinished?()
    }
}

req 中以 weak 的方式持有了 delegate,因为网络请求是一个异步过程,很可能会遇到用户不愿意等待而选择放弃的情况。这种情况下一般都会将 RequestManager 进行清理,所以我们其实是无法保证在拿到返回时作为 delegate 的 RequestManager 对象是一定存在的。因此我们使用了 weak 而非 unowned,并在调用前进行了判断。

闭包和循环引用

另一种闭包的情况稍微复杂一些:我们首先要知道,闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了 self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用。最简单的例子是,我们声明了一个闭包用来以特定的形式打印 self 中的一个字符串:

class Person {
    let name: String
    lazy var printName: ()->() = {
        print("The name is \(self.name)")
    }

    init(personName: String) {
        name = personName
    }

    deinit {
        print("Person deinit \(self.name)")
    }
}

var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:
// The name is XiaoMing,没有被释放

printName 是 self 的属性,会被 self 持有,而它本身又在闭包内持有 self,这导致了 xiaoMing 的 deinit 在自身超过作用域后还是没有被调用,也就是没有被释放。为了解决这种闭包内的循环引用,我们需要在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用。可以将 printName 修改为这样:

lazy var printName: ()->() = {
    [weak self] in
    if let strongSelf = self {
        print("The name is \(strongSelf.name)")
    }
}

现在内存释放就正确了:

// 输出:
// The name is XiaoMing
// Person deinit XiaoMing

如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned。

这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在原来参数的前面,并使用中括号括起来。如果有多个需要标注的元素的话,在同一个中括号内用逗号隔开,举个例子:

// 标注前
{ (number: Int) -> Bool in
    //...
    return true
}

// 标注后
{ [unowned self, weak someObject] (number: Int) -> Bool in
    //...
    return true
}

原文链接http://swifter.tips/retain-cycle/?utm_source=tuicool&utm_medium=referral

C语言—for循环打印矩形,正三角形,倒三角形,九九乘法,等腰三角形,倒着的等腰三角形(简易版代码)(重点考察for循环嵌套) 本章节教大家如何利用for循环打印正三角形,倒三角形,九九乘法表及等腰三角形(简易版代码)说明:1、循环语句的循环体内又包含另一个完整的循环结构,称为循环结构的嵌套。内嵌的循环语句中还可以再嵌套循环结构,这样就形成多重循环结构的嵌套。2、for 语句、while 语句和 do-while 语句都允许嵌套。三种循环语句可以相互嵌套。3、内层的循环语句必须完全被包含在外层的循环体中,不能发生交叉。 阅读详情

相关推荐

实战:如何用MIPI CSI-2高效传输RAW10图像数据(附带宽优化技巧)

本文深入探讨了MIPI CSI-2协议在RAW10图像数据传输中的高效应用,详细解析了4像素5字节打包机制及其带宽优化技巧。通过动态Lane分配、位深度自适应压缩和虚拟通道交错传输等实战方法,显著提升传输效率,适用于IoT摄像头开发等低功耗场景。

css33的专栏 731

内存管理,WEAK 和 UNOWNED

内存管理,WEAK 和 UNOWNED 由 王巍 (@ONEVCAT) 发布于 2015/02/25 因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行。在 Playground 中可能无法得到正确的结果。 不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。Swift 是自动管

zhangqixiang5449的博客 334

PSM倾向匹配详细步骤和程序

目录1.安装psmatch2统计包。2.数据准备3.数据分析及命令解读4.结果解读4.1模型拟合结果,此处无太多实际意义。4.2试验组可匹配的观测概览,按照命令中设定的匹配规则,试验组有8例患者未能匹配到合适对照。4.3结果解读的重点应该是对stata新生成的中间变量的解读。4.4均衡性检验结果4.5匹配结果的图示化5.Stata命令汇总最后留下两个问题给大家思考:题目 试验设计中,匹配的目的在于确保干预效应估计是建立在可比个体之间的不同结果的基础上。最简单的匹配方式是将干预组和对照组中协变量值相同的两个个

平平淡淡,戒急用忍,一生学闭嘴。 5万+

JSP内置对象的常用方法

request对象 getParameter() 取得请求中指定的参数值 getParameterValues() 将同名的参数一次性读入到String类型的数组中 getParameterNames() 获取参数名称,返回的是枚举类型 getMethod() 获取客户提交信息的方式,如post或get getServletPath() 获取JSP页面文件的目录 getHeader() 获取HTTP头文件中的指定值 getR...

Guard_Jun的博客 439

MaxKey+JeecgBoot单点登录实战:手把手教你从零配置到账户同步

本文提供MaxKey与JeecgBoot单点登录(SSO)的实战整合指南。详细讲解从MaxKey应用注册、JeecgBoot前后端改造,到最终实现账户同步的完整步骤,重点解决Shiro配置、令牌交换等关键问题,帮助企业构建统一、可扩展的身份管理体系。

weixin_29258297的博客 541

把核心变干净的代价与回报:为什么推动 Clean Core,几乎绕不开 SAP Integration Suite 与 API Management

Clean Core的核心价值在于构建可持续升级的ERP系统架构,其关键不在于减少代码或限制扩展,而是通过解耦和治理实现系统边界的清晰划分。这一理念将推动企业从传统的"私搭乱建"式集成向平台化工程转型,而SAP Integration Suite和API Management正是实现这一转型的核心组件。 Integration Suite作为集成中枢,通过统一平台、可复用内容库和事件驱动架构,将碎片化连接转变为标准化能力;API Management则负责建立接口治理体系,通过统一入口、

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 241

Swift - 内存管理,weak 和 unowned

不管在什么语言里,内存管理的内容都很重要,所以我打算花比较长的篇幅仔细的说说这块内容。 Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的过程遵循了自动引用计数(ARC)的规则:当一个对象没有引用的时候,其内存会被自动回收。这套机制很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空(比如超过作用域,或者手动设为nil等),就可以确保内存使用不出现问题。 但是,所有的自动引用计数机制都有一个从理

HumorousGhost 1150

R语言单因素被试内ANOVA检验

在R语言中,我们可以使用多种方法进行被试内ANOVA检验,其中包括使用内建函数或使用外部包。我们随机选择了10个被试,并对每个被试使用了三种不同的药物处理。通过上述代码示例,我们可以使用R语言进行单因素被试内ANOVA检验。假设我们的数据存储在一个名为"data"的数据框中,其中包含两个列:一个列是被试的编号,另一个列是生物指标的测量值。在上述代码中,我们创建了一个名为"data"的数据框,其中"Subject"列包含了被试的编号,"Measurement"列包含了生物指标的测量值。参数指定了数据来源。

PixelNovaO的博客 363

Swift 内存管理,WEAK 和 UNOWNED 【转】

因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行。在 Playground 中可能无法得到正确的结果。 不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。 Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过...

dehuai19940321的博客 151

v86.01 鸿蒙内核源码分析 (静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码

相比动态分配,静态内存池的分配就是个小弟弟,非常的简单,两个结构体 + 一张图 就能说明白。

日拱一卒,力出一孔 775

GVINS数据采集(u-blox fp9 + D435i)

1、配置千寻的RTK账号 采集数据当天购买千寻的RTK账号,然后在Window下,输入账号和密码 (具体的步骤如之前面连接所示),查看u-center软件上的信息,确保接收机可以收到RTK信息; 2、查看Linux系统下u-blox是否可以正常接受GPS信号和RTK数据流 1、GPS信号信息 // 打开端口 1 cd ublox_f9p/src // 或者在终端1下也可以 source devel/setup.bash r.....................

努力努力努力 2609

Swift开发必备技巧:内存管理、weak和unowned

因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行。在 Playground 中可能无法得到正确的结果。 不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。 Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Sw

u013406800的专栏 5314

基于YOLO26深度学习框架的人脸识别门禁系统的算法研究实现方法

本文研究了基于YOLO26深度学习框架的人脸识别门禁系统实现方法。系统采用YOLO26目标检测算法,通过CSPDarknet53主干网络、特征金字塔结构和注意力机制实现高效人脸检测。研究内容包括数据预处理、模型训练优化和身份验证等关键技术,使用迁移学习策略和CIoU损失函数提升检测精度。该系统可应用于智能安防场景,具有实时性强、识别准确的特点,为门禁管理提供了智能化解决方案。

一键难忘的博客 766

swift 内存管理,WEAK 和 UNOWNED

因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行。在 Playground 中可能无法得到正确的结果。 不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。 Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Sw...

weixin_30882895的博客 287

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Swift里的内存管理:内存管理、weak和unowned

因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行。在 Playground 中可能无法得到正确的结果。 不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。 Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Sw

zyx196的博客 489

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Swift内存管理、weak和unowned以及两者区别(如何使用Swift 中的weak与unowned?)

Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。 当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。 这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为nil等),就可以确保内存使用不出现问...

weixin_30407099的博客 160

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

swift [unowned self] 和 [weak self]区别 ObjC weak和 assign区别

weak 一般我们用来修饰delegate ,block中使用 __weak typeof(self) weakSelf = self; 这两者都是为了避免产生循环引用 循环引用的产生(如): @class Dog; @interface Person : NSObject ///人有一条宠物狗 @property (nonatomic,strong) Dog *dog; @end ...

aasdsjk的博客 5264

Swift 弱引用 (weak )与 无主引用 (unowned )的区别 [Debug]

1.先举一个循环引用的列子 class Example { var num = 10 var method:(Int) -> Int = { (i:Int) in return self.num + i } deinit { print("Example被释放了") } } method 是一个闭包类型的参数,在类的定义中被初始化为与类属性 num 进行加法的运算。 使用Example: var example:Example? = Example() examp

taoqick的专栏 797

Swift中的weak和unowned关键字

Swift中没有了strong, assign, copy属性修饰符,对于所有的class类型变量都默认采用了strong类型,如果需要指定使用weak,则需要添加weak关键字修饰。 正是由于这种默认的strong类型,在闭包中会引起循环引用,导致内存无法释放,为了能够在闭包(block)中正常释放内存,需要对闭包中的self使用弱引用,也就是不持有self对象。 如下面的代码中: class UserInfoViewController { // ... private lazy var

Rey的技术博客 1111

ios weak和unown_Unowned 还是 Weak?生命周期和性能对比

性能:深度探索捕获列表处理解析结论脚注从 GitHub 或者 zipped 获取本文相关的 Playground 代码。然后从这 里获取闭包案例和 SIL,SILGen 以及 LLVM IR 的输出。从 GitHub 或者 zipped 获取本文相关的 Playground 代码。然后从这 里获取闭包案例和 SIL,SILGen 以及 LLVM IR 的输出。基础知识众所周知,Swift 利用古老...

weixin_39932181的博客 189

Swift 内存管理与循环引用问题(weak、unowned)

之前我在优快云上写过一篇博客:OC内存管理、ARC、property属性、__strong、__weak(),大家有兴趣的可以去看看。 今天我们来整理一下Swift的内存管理与循环引用的解决问题-weak、unowned: 内存管理 swift的内存管理也是使用的ARC(自动引用技术):当我们初始化创建一个对象实例的时候,swift就会替我们管理和分配内存,此时的引用计数为1,当对

u012297622的博客 5000

Swift学习:内存管理,weak,unowned,autoreleasepool,循环引用,escaping,指针

内存管理: 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间) class Person { deinit { print("Person.deinit") } } print(1) var p: Person? = Person() print(2) p = nil print(3) //输出结果为: //1 //2 //Person.deinit //3 由上图可以看出,p置为nil时,对象销毁,走deinit方法 Swi.

qq_41240287的博客 3350
上一篇: IOS UITableView中UITextField 弹出键盘时,界面上移
下一篇: 将 PROTOCOL 的方法声明为 MUTATING
小伞_
博客等级 码龄13年 1粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值