iOS - 一个崩溃 SIGSEGV / SEGV_ACCERR

起因

Bugly 上出现了一个崩溃日志 SIGSEGV/SEGV_ACCERR。

分析

一个内存非法引用问题,看了下堆栈,崩溃时最后一行代码是:

self.lastBestNode.focused = NO;

怎么属性访问还能出现非法内存呢?非法内存访问最常见的就是访问已经释放了的对象的指针,看上面这句话其实是两个调用:

node = self.lastBestNode;
node.focused = NO;

于是先检查了一下 lastBestNodefocused 这两个属性的定义情况:

focused 属性比较简单,就是一个 BOOL 属性,直接用的 @synthesize focused; 生成 getter 和 setter,应该不会有什么问题。

@property (assign, nonatomic) BOOL focused;

lastBestNode 有点不寻常了,它是在 category 中定义的,使用了 runtime 中的 objc_getAssociatedObjectobjc_setAssociatedObject

- (SCNNode<RadarObjectNode> *)lastBestNode {
    SCNNode<RadarObjectNode> *node = objc_getAssociatedObject(self, _cmd);
    return node;
}
- (void)setLastBestNode:(SCNNode<RadarObjectNode> *)bestNode {
    objc_setAssociatedObject(self, @selector(lastBestNode), bestNode, 
        OBJC_ASSOCIATION_ASSIGN);
}

定睛一看,原来 AssociationPolicy 设置为了 OBJC_ASSOCIATION_ASSIGN,也就是 weak 的含义,大概就是这里的问题了。至于这里为啥子要设置为 weak,是谁干的,经过 git blame,发现是当年还是小菜鸟的我寄几😭。

weak 修饰的变量和属性有一个特点,当指向的对象被释放后,它的值会自动更新为 nil。因此就理(mei)所(you)当(si)然(kao)地以为直接使用 runtime AssociatedObject 相关方法也能达到这个效果……

于是先面向百度和谷歌搜索一番,得到的答案都是没有自动设置为 nil 的效果。

https://nshipster.com/associated-objects/
Weak associations to objects made with OBJC_ASSOCIATION_ASSIGN are not zero weak references, but rather follow a behavior similar to unsafe_unretained, which means that one should be cautious when accessing weakly associated objects within an implementation.

验证

目标:使用 OBJC_ASSOCIATION_ASSIGN 设置的关联并没有对象释放后自动设置为 nil 的功能。

  1. 创建一个空的 iOS 项目。
  2. 新建一个类 MyObject,重写 dealloc 方法,方便打印 log 查看什么时候被释放了。
  3. 在 ViewController 里定义一个属性:
@property (nonatomic, strong) MyObject *object;
  1. viewDidLoaded 中初始化一下这个属性
self.object = [[MyObject alloc] init];
  1. 接着,使用 OBJC_ASSOCIATION_ASSIGN 类型关联到 ViewController。
objc_setAssociatedObject(self, key, self.object, OBJC_ASSOCIATION_ASSIGN);
  1. 加两个按钮:一个释放 self.object,另一个使用 objc_getAssociatedObject 读取。
// 释放
self.object = nil;
// 读取
objc_getAssociatedObject(self, key);

运行:点击读取按钮,根据打印 log 正常读取到了值。先释放再读取,崩了。

解决

两种方案:

  • 直接改成 OBJC_ASSOCIATION_RETAIN_NONATOMIC 使用强引用。
  • 使用 weak 关键字来保证释放后自动设为 nil

使用哪个取决于是否应该持有对象,也就是强引用对象。第一种方案很简单,改下参数就行了,因为持有这个对象也是合理的,因此实际项目中用的这个简单方法改的。下面说一下第二种方案:
如何利用 weak 关键字实现关联对象的自动释放。

俗话说得好,没有添加中间层解决不了的问题,恩,我们来自定义一个中间层对象,就叫它 Wrapper 吧。

@interface Wrapper : NSObject
@property (nonatomic, weak) id object;
@end

关联对象时使用 Wrapper 包装一下,这样就可以利用 Wrapper 中的 weak 属性获得释放后设置为 nil 的能力了。

- (MyObject *)object {
    MyWrapper *wrapper = objc_getAssociatedObject(self, _cmd);
    return wrapper.object;
}
- (void)setObject:(MyObject *)object {
    SEL key = @selector(object);
    MyWrapper *wrapper = objc_getAssociatedObject(self, key);
    if (wrapper == nil) {
        wrapper = [[MyWrapper alloc] init];
        objc_setAssociatedObject(self, key, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    wrapper.object = object;
}

结论

关联对象时 objc_setAssociatedObject 不应该使用 OBJC_ASSOCIATION_ASSIGN

OBJC_ASSOCIATION_ASSIGN 关联的对象并不具备“释放后自动设置为 nil ” 的功能。因为基础类型无法进行关联,必须转化为对象类型,而使用 OBJC_ASSOCIATION_ASSIGN 关联的对象又有释放后再访问崩溃的隐患。因此 OBJC_ASSOCIATION_ASSIGN 的使用场景非常少,建议不使用。

发散

为什么 weak 关键字有这么大的魔力,能判断出对象被释放了?

一句话解释:因为有内部的表去记录所有的 weak 引用,释放对象时更新这个表中的数据,weak 引用就知道应该设置为 nil

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容