Objective-C中的associated object释放时机问题
如果对象A持有对象B,B作为A的associated object,并且表面上B没有其他被强引用的地方,那么对象A被释放时,对象B一定会同时释放吗?大部分情况下是,但真有不是的时候。最近实现代码的时候不小心就碰到了这样的特殊情况。
需求
需要监听对象A释放(dealloc)并执行对象A的a方法。此时引入对象B,并作为对象A的associated object。A释放时触发B释放,在B的dealloc方法中执行A的a方法。对象B需要一个指向对象A的属性,并声明为unsafe_unretained(或assign),因为weak指针此时已经失效了。
示例代码
@interface MyObject1 : NSObject @end @implementation MyObject1 - (void)foo { NSLog(@"success"); } @end @interface MyObject2 : NSObject @property (nonatomic, unsafe_unretained) MyObject1 *obj1; @end @implementation MyObject2 - (void)dealloc { [self.obj1 foo]; } + (instancetype)create { return [[self class] new]; } @end @implementation ViewController + (void)load { [self fun1]; } + (void)fun1 { MyObject1 *mo1 = [MyObject1 new]; @synchronized (self) { MyObject2 *mo2 = [MyObject2 create]; mo2.obj1 = mo1; objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } @end
问题
运行时出现崩溃,unsafe_unretained指针已经野了,和预期的不一样。堆栈是这样的:
观察崩溃的堆栈,发现mo2
对象是被自动释放池释放了。因为mo1
对象是在函数退出时就立即释放,这样导致mo1
比mo2
先被销毁,mo2
访问了无效指针导致了崩溃。
这个问题和@synchronized
有关系,但目前我还不知道它和arc之间有什么联系。下面给出另一个case,修改一行代码就不会崩溃了:
+ (void)fun2 { MyObject1 *mo1 = [MyObject1 new]; MyObject2 *mo2 = [MyObject2 create]; @synchronized (self) { mo2.obj1 = mo1; objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } }
实际上只是把mo2
的声明移动到了@synchronized
外面,堆栈变成了这样:
这时,mo2
的释放发生在调用方法的结束时。
分析
使用Hooper查看汇编代码,观察fun1
和fun2
的不同。节选出关键部分:
fun1:
fun2:
核心在于:fun1
中,创建mo2
后调用了retain
,fun2
中,调用的则是objc_retainAutoreleasedReturnValue
。
我们再来看看create
方法:
关键的一行在最后,调用了objc_autoreleaseReturnValue
。
关于objc_retainAutoreleasedReturnValue
和objc_autoreleaseReturnValue
,请移步 https://www.jianshu.com/p/2f05060fa377 。大意是,这两个方法成对出现时,可以优化掉[[obj autorelease] retain]
这种骚操作。
结论
在fun1
中,由于没有objc_retainAutoreleasedReturnValue
,取而代之的是retain
,导致对象被放入自动释放池。对于@synchronized
为什么会造成不同,我还没有那么深入。
因为全局自动释放池会延迟对象的释放,如果代码非常依赖对象的释放时机则会比较危险。我认为这样做是最保险的,创建一个局部自动释放池,保证局部变量在函数结束时立即释放:
+ (void)fun3 { MyObject1 *mo1 = [MyObject1 new]; @autoreleasepool { @synchronized (self) { MyObject2 *mo2 = [MyObject2 create]; mo2.obj1 = mo1; objc_setAssociatedObject(mo1, @selector(viewDidLoad), mo2, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } }
参考资料
objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue函数对ARC的优化 https://www.jianshu.com/p/2f05060fa377
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Gradle排除依赖模块的某个类
好久没写文章了,开工第一天来一发开门红。既然没什么可写的,就简单聊聊Gradlew吧,是Gradle,命令敲多了习惯加个w,开个小玩笑。gradle用来构建项目可谓是十分的灵活,刚开始接触也许觉得有点懵,觉得这个东西就是多余的,但是用多了就真的会爱上它。我之前做组件化的时候就碰到锅这样一个问题,怎么排除某个类 Gradle怎么排除所依赖的模块的某各类 其实我觉得在很多的情况下都会碰到这个问题,比如你的老大写了个BaseModule,里面有很多东西,你就没必要造轮子了,但是其实你这个项目中用不到这么多,那就没必要在打包的时候也把多余的代码打进去,但是你又不能删掉,因为很多个模块都依赖这个Base模块,所以你只能用“排除”的方法来去掉多余的代码。再比如你自己的模块和所依赖的模块有同名类,包名都相同的那种,那就肯定编译不过,所以你要排除相同类。甚至还有很多情况需要你在依赖中排除某个类或者某个包。那么该怎么做?只要稍微用过gradle构建项目的都知道,如果我们在依赖时出现了相同的jar包,我们是可以排除的 implementation fileTree(include: ['*.jar'], ...
- 下一篇
2.2019Android高级面试题总结
说下你所知道的设计模式与使用场景 a.建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如Camera开发过程中,可能需要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可以使用建造者模式装饰者模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。装饰者模式可以在不改变原有类结构的情况下曾强类的功能,比如Java中的BufferedInputStream 包装FileInputStream,举个开发中的例子,比如在我们现有网络框架上需要增加新的功能,那么再包装一层即可,装饰者模式解决了继承存在的一些问题,比如多层继承代码的臃肿,使代码逻辑更清晰观察者模式:代理模式:门面模式:单例模式:生产者消费者模式: java语言的特点与OOP思想 这个通过对比来描述,比如面向对象和面向过程的对比,针对这两种思想的对比,还可以举个开发中的例子,比如播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,比如,加载视频地...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8编译安装MySQL8.0.19