当前位置: 首页 > news >正文

重拾 ObjC 自动释放池

Objc 自动释放池平时很少显式的使用,但其实它时刻在默默为我们工作。关于自动释放池源码分析的文章已经很多了,本文不会在源码层面剖析原理。

初衷

在 MRC 时代,需要使用retainrelease手动维护对象的引用计数,并要遵循「谁创建谁释放」的原则。

然而在某些场景下无法满足这个原则,比如说工厂方法:

+ (id)factory {
    return [self new];
}
复制代码

return处如果调用retain,就需要调用方负责 release,这显然是不科学的设计。所以这里不能retain, 但是不 retain,该对象超出作用域后就会被释放,调用方取到的会是 nil。该如何保证调用方在这个对象超出作用域后,还能取到呢?方法就是自动释放池,在返回前,将该对象被加入自动释放池,这样调用方就能顺利取到返回值了。

那如果对象真的需要被释放了,如何从自动释放池里移除?熟悉 RunLoop 的同学应该知道,在 RunLoop 唤醒和即将睡眠状态之间会被插入自动释放池,每次 RunLoop 迭代都会向本次迭代加入的对象发送一条release 消息。如果对象的引用计数变为 0,便会被释放。

实现和实践

自动释放池虽然被叫做”池“,其实它是一个栈结构。栈的实现方式有很多,自动释放池采用了双向链表,能够比较方便的实现 push 和 pop。

自动释放池之所有用栈实现,而不用其他数据结构,比如说散列表?是因为 autorelease 对象往往需要批量处理,比如说一次 RunLoop 迭代生成一大批的 autorelease 对象。

所以这里就出现一个问题,假如某一段代码在一次 RunLoop 周期内生成大量的 autorelease 对象,还没有等到迭代结束清理,就已经内存溢出了,该怎么办?

这是就需要手动的来触发 autorelease 对象的释放。@autoreleasepool{}登场,它能够控制 autorelease 对象释放的颗粒度。

{
	for (NSInteger i = 0; i < 10000; i++) {
		@autoreleasepool {
		    NSImage *img = [NSImage imageNamed:@"aimge"];
		}
	}
}
复制代码

上述的极端例子,如果for循环中不嵌套 autoreleasepool,在 Xcode 侧边栏的 Debug Session,能看到应用的占用的内存不断的增加。

在嵌套使用 autoreleasepool 的场景,并且需要由内而外逐层清理,所以使用栈最适合不过了。

钉子和锤子

以前面试几乎每次都会被问对象是什么时候释放的,一般的回答是引用计数为 0,但这只是站在对象的角度考虑的,那什么时候对象的引用计数会变为 0 呢?

因为了解过自动释放池,所以会说是在 autoreleasepool pop 的时候,如果没有手动的添加 autoreleasepool 便会在 RunLoop 迭代的时候引用计数被减为 0 时释放。

其实回答的有些片面,并不是所有对象都是在 pop 的时候引用计数才会为 0 的,普通局部对象其实在超出作用域时(大括号)引用计数为 0,就会被立即释放。

  1. 普通局部对象 局部对象在超出作用域并且引用计数为 0 时会立即释放。
override func viewDidLoad() {
	super.viewDidLoad()
	let o = NSObject()
}
// `o` has release
复制代码
  1. autorelease 对象
override func viewDidLoad() {
	super.viewDidLoad()
	let imgO = UIImage(named: “image”);
}
// imgO 还未释放,会等到 autoreleasepool pop 才释放。
// 可使用 weak 指针测试,在 viewWillApper 方法该对象仍然存在
// 或使用符号断点,检测 AutoreleasePoolPage::autorelease() 方法被调用
复制代码

转载于:https://juejin.im/post/5c8dbb1ae51d455b9c2b6a41

相关文章:

  • 监听JS对象属性变化 Object.defineProperty Proxy 记录
  • 读ios开发有感——建立APP开发体系
  • 回归
  • Kubernetes — 重新认识Docker容器
  • 专业术语------扫盲
  • 实验1
  • nunjucks模版引擎入门
  • git flow常用命令
  • PHP实现多维数组按指定值排序
  • 高分笔记_括号匹配
  • 2018-2019-2 《网络对抗技术》Exp2 后门原理与应用 20165211
  • 每日 30 秒 ⏱ 谁敢与我一战
  • 用Python爬取王者农药英雄皮肤
  • 杂记:Python 两坑
  • Sass预处理器常用功能(OneLine周分享)
  • 【技术性】Search知识
  • 2019.2.20 c++ 知识梳理
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • ComponentOne 2017 V2版本正式发布
  • CSS 提示工具(Tooltip)
  • ECMAScript入门(七)--Module语法
  • TCP拥塞控制
  • Vue UI框架库开发介绍
  • 从setTimeout-setInterval看JS线程
  • 关于extract.autodesk.io的一些说明
  • 汉诺塔算法
  • 如何设计一个微型分布式架构?
  • 三栏布局总结
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 数据库巡检项
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • #Java第九次作业--输入输出流和文件操作
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (三)Honghu Cloud云架构一定时调度平台
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (转)Windows2003安全设置/维护
  • (转)树状数组
  • *** 2003
  • ... 是什么 ?... 有什么用处?
  • .axf 转化 .bin文件 的方法
  • .NET delegate 委托 、 Event 事件
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .NET是什么
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • @hook扩展分析
  • [Android] 修改设备访问权限
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [C/C++]数据结构 堆的详解
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行
  • [EFI]Lenovo ThinkPad X280电脑 Hackintosh 黑苹果引导文件
  • [git]git命令如何取消先前的配置