一道网易面试题
一、题目描述
题目来自网上一个博客,具体类似如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @interface ViewController () @property ( nonatomic , strong) NSString *target; @end @implementation ViewController - ( void )viewDidLoad { [ super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. dispatch_queue_t queue = dispatch_queue_create( "parallel" , DISPATCH_QUEUE_CONCURRENT); for ( int i = 0; i < 1000000000 ; i++) { dispatch_async(queue, ^{ self .target = [ NSString stringWithFormat:@ "ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d" ,i]; NSLog (@ "%@" , self .target); }); } } |
问代码执行之后会发生什么?
二、解析
在设置target的setter中,是非线程安全的,未加锁;因此多线程访问这个属性setter方法的时候潜在crash的情况
因为setter大概如下
1 2 3 4 5 6 7 8 | - ( void )setTarget:( NSString *)target { if (_target != target) { [_target release]; _target = [target retain]; } } |
对应runtime代码
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 | //objc_class.mm void object_setIvar( id obj, Ivar ivar, id value) { return _object_setIvar(obj, ivar, value, false /*not strong default*/ ); } static ALWAYS_INLINE void _object_setIvar( id obj, Ivar ivar, id value, bool assumeStrong) { //判断是否是TaggedPointer if (!obj || !ivar || obj->isTaggedPointer()) return ; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; //找对应的内存管理语义和属性偏移值 _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); //如果找不到默认是否为Strong,不然为unsafe_unretained if (memoryManagement == objc_ivar_memoryUnknown) { if (assumeStrong) memoryManagement = objc_ivar_memoryStrong; else memoryManagement = objc_ivar_memoryUnretained; } //根据偏移值找到属性对应位置 id *location = ( id *)(( char *)obj + offset); //判断不同的内存管理语义,调用方法 switch (memoryManagement) { case objc_ivar_memoryWeak: objc_storeWeak(location, value); break ; case objc_ivar_memoryStrong: objc_storeStrong(location, value); break ; case objc_ivar_memoryUnretained: *location = value; break ; case objc_ivar_memoryUnknown: _objc_fatal( "impossible" ); } } |
在release的方法最后会调用obj_release
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //NSObject.mm void objc_storeStrong( id *location, id obj) { //如果新值指针和旧值一样,则不更新,直接return id prev = *location; if (obj == prev) { return ; } //先对新值retain objc_retain(obj); //再赋值 *location = obj; //最后对旧值release objc_release(prev); } |
因为一个对象已经release了,但是这个指针指向的内存已经被回收,所以访问这个指针的内存会产生一个内存访问的错误
1 | 2018-11-09 15:22:04.860819+0800 ARCTest2[93017:2107037] *** -[CFString release]: message sent to deallocated instance 0x600000e70240 |
以上的代码用模拟器是比较容易出现的,因为GCD创建了64个线程,线程并发次数很多
如果使用iPhoneX的话,没有出现,(应该是比较难重现),但是存在crash的可能
可以看到GCD创建了6个线程,是6核的1倍
如果将target的修饰改为atomic,将不会crash,但是直接访问实例变量依旧会产生crash。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @interface ViewController () @property (atomic, strong) NSString *target; @end @implementation ViewController - ( void )viewDidLoad { [ super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. dispatch_queue_t queue = dispatch_queue_create( "parallel" , DISPATCH_QUEUE_CONCURRENT); for ( int i = 0; i < 1000000000 ; i++) { dispatch_async(queue, ^{ _target = [ NSString stringWithFormat:@ "ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d" ,i]; NSLog (@ "%@" , self .target); }); } } |
三、总结
通过上面的例子,我们可以总结出来,对于一个变量,如果多线程访问之下,retain、release的顺序得不到保证的话,就会带来野指针的问题
ARC只能保证在合适的地方插入retain、release;但是retain、release的顺序还需要业务来进行保证。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架