Swift 内存管理详解
Swift内存管理:
Swift 和 OC 用的都是ARC的内存管理机制,它们通过 ARC 可以很好的管理对象的回收,大部分的时候,程序猿无需关心 Swift 对象的回收。
注意:
只有引用类型变量所引用的对象才需要使用引用计数器进行管理,对于枚举、结构体等,他们都是值类型的。因此不需要使用引用计数进行管理。
一:理解ARC
1: ARC 自动统计改对象被多少引用变量引用,这个值就是我们常说的引用计数器。
2: 每当引用计数器计数变为0的时候,ARC就会回收这个对象。
比如,现在我们做一个针对大学生用户的APP,我们写了一个User类,这个类里面有姓名、年纪、班级三个属性,看整个文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import UIKit class ComNavigationController : UINavigationController { class User { var name : String var age : Int var classes : String init ( name : String , age : Int , classes : String ) { self . name = name self . age = age self . classes = classes } deinit { print ( "\( self . name ) 用户即将被销毁" ) } } override func viewDidLoad () { var user1 : User ? user1 = User ( name : "zhangxu" , age : 24 , classes : "三年二班" ) /// 创建了一个User对象 用户1这个变量是指向User对象的,这时候User对象的引用计数为1 var user2 : User ? user2 = user1 var user3 : User ? user3 = user1 // 这时候被变量2 和变量3 都引用了,User对象的引用计数就变成了------ 3 print ( user2 ) print ( user3 ) user1 = nil user2 = nil user3 = nil // 1 2 3 都置为你了 用户都不再引用User对象 // 这时候 User对象不被任何变量引用,引用计数器就变成了0 // 引用计数器编程了 0 ,ARC就会回收该对象 super . viewDidLoad () // Do any additional setup after loading the view. } override func didReceiveMemoryWarning () { super . didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } |
二:强引用循环
大部分时候,ARC能够很好的处理程序中对象的内存回收,但如果这两个对象之间存在着相互的引用,也就是当两个对象都使用存储属性相互的引用对方的时候,此时两个对象的引用计数都等于 1 ,但实际上它们都没有被真正的引用变量所引用,就像上面的 user1 这样的变量。这时候的 ARC是无法回收它们的。
看下面的代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class teacher { var name : String var age : Int var student1 : student ? init ( name : String , age : Int ) { self . name = name self . age = age } deinit { print ( "老师对象被回收" ); } } class student { var name : String var age : Int var teacher1 : teacher ? init ( name : String , age : Int ) { self . name = name self . age = age } deinit { print ( "老师对象被回收" ); } } var stu : student ? = student ( name : "zhangxiaxu" , age : 24 ) var tea : teacher ? = teacher ( name : "wangnima" , age : 200 ) // 就在这里相互引用,形成了强引用循环 stu ?. teacher1 = tea tea ?. student1 = stu stu = nil tea = nil |
解释一下:
上面的代码执行完之后,两个对象之间不再有真正的引用变量引用他们,但两个对象之间的相互引用,形成了"强引用循环",此时它们的引用计数为 1 ,ARC也不会去回收它们,任何一个对象释放,都要等对方先释放,因此两个对象你谁都没办法被回收,为了结束上面的强引用循环,我们就必须让一方先放手,允许对方先释放。Swift这时候提供了两种机制: 弱引用和无主引用
三:使用弱引用解决强引用循环
弱引用不会增加对方的引用计数,因此不会阻止ARC回收被引用的实例,这样就避免了形成强引用循环, 在定义属性的 var 关键字之前加 weak 就定义了弱引用。
注意点:
1 : 弱引用变量要求该变量必须要能被设置成 nil ,也就是弱引用的属性最好是使用可选类型来定义。
2 : 弱引用的属性只能声明为变量类型,因为该属性在运行期内只有可能会发生变化,因此不能设置成常量。
3 :也没必要把两个相互引用的属性都设置成弱引用,有一个就可以了。
所以,要是使用弱引用解决上面的强引用循环的,只需按下面声明属性:
1 2 3 4 5 | // 修改teacher类的 student 为弱引用属性 weak var student1 : student ? // 或者修改 student 类的 teacher 为弱引用属性 weak var teacher1 : teacher ? |
四:使用无主引用解决强引用循环
与弱引用相似的是,无主引用也不会增加对方的引用计数,无主引用于弱引用的区别:
无主引用不允许接受nil,意思就是这个属性要一直有值!因此无主引用只能定义为非可选类型。
在定义属性 var 或者 let 之前,添加 unowned 关键字即可。上面的强引用要用无主引用解决的话,看下面代码:
1 2 3 4 5 | // 声明 teacher 类的 student 属性为无主引用 且 不能是可选类型。 unowned let student1 : student // 或者声明 student 类的 teacher 属性为无主引用 unowned let teacher1 : teacher |
五:闭包的强引用循环解决
上面给出了两种方式,说说他们的使用场景的一个区别。
当闭包和捕获的对象总是相互引用,并且总是同事销毁时,应该将闭包内捕获的实例定义为无主引用。
当闭包捕获的引用变量有可能是 nil 时,将闭包捕获的引用变量定义为弱引用。
如果程序将该对象本身传入了闭包,那么闭包本身就会捕获该对象,于是该对象就持有了闭包属性,反过来,闭包也持有对象,这样子就形成了强引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import UIKit class ComNavigationController : UINavigationController { class teacher { var name : String var age : Int lazy var findteacher :() - > String = { [ unowned self ] in return "该老师名字是\( self . name ) 年纪是\( self . age )" //[weak self] in //return "该老师名字是\(self!.name) 年纪是\(self!.age)" } init ( name : String , age : Int ) { self . name = name self . age = age } deinit { print ( "老师对象被回收" ); } } override func viewDidLoad () { super . viewDidLoad () var tea : teacher ? = teacher ( name : "葫芦娃" , age : 24 ) var find :(() - > String )? = tea !. findteacher tea = nil find = nil // Do any additional setup after loading the view. } override func didReceiveMemoryWarning () { super . didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } } |
解释一下:
上面代码中,我们看在 viewdidload 方法中,先创建了一个 teacher 对象,并且赋值给 tea 变量,接下来有定义了一个函数类型的变量,并且将 teacher 实例的 findteacher 属性赋值给该变量,到后面tea 和 find 变量都赋值为 nil , 此时没有引用变量引用 teacher 对象和闭包对象,但两个对象之间的相互引用就形成了强引用循环。
当然,我们只是说形成了,上面的代码里面也已经给出了解决的方法,尤其注意一点,就是使用无主引用和弱引用时候 self 的区别。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话