不安全的weak变量

对于存在多线程释放并且并发访问的对象,不建议使用weak修饰或访问。因为weak的底层实现并不完全是线程安全,否则较容易导致over-release而crash。

一、问题

每次版本升级初期总是有少部分会遇到如下的crash

请在这里填写图片描述

虽然量很少,但总是有也很是烦人。没办法只能看下到底是怎么回事。

二、问题描述

很明显,这是一个over-release的问题;挂在objc_release里;

业务代码如下:

- (void)getIconImage:(NSString*)uri
{
    DefineWeakSelfBeforeBlock();
    NSString * fullURLString = uri;
    if ([fullURLString hasPrefix:@"//"]) {
        fullURLString = [NSString stringWithFormat:@"http:%@", fullURLString];
    }
    NSURL *url = [fullURLString createURL];
    NSString *title=self.title;
    
    [self download:url completion:^(NSData *data, NSError *error) {
        DefineStrongSelfInBlock(sself);
        UIImage *image=nil;
        if(data)
        {
            // 进入自绘逻辑
            UIImage * reseponseImage = [UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
            if (reseponseImage) {
                UIColor * color = [reseponseImage getPresentColor];
                image = [UIQuicklinkImageRenderer defaultIconImageWithTitle:title backgroundColor:color];
            }
        }
        if(!image)
        {
            image = DEFAULT_ICON(title);
        }
        [sself doCompletion:image error:error];
    }];
}

crash堆栈不够直观,但是可以简单猜测出最后访问sself时肯定出现了over-release了;
但是这是标准的ARC代码,怎么会出现over-release呢?除非编译器干了什么,或者objc本身出错了吧?

三、问题分析

这个问题发生时有一个可能比较多的场景是,多线程多次并发回调该函数时,因此这个问题很快就能找到原因了;

既然单独看代码看不出什么问题,那就直接上汇编找找线索;
根据猜测crash很可能是sself的over-release问题,那为什么这里会触发了release了呢?

objcloadWeakRetained

weak的sself访问,在这里被ARC转换为了objcloadWeakRetained函数,然后又对retain的该指针,进行了objc_release,汇编代码如下:

如上的代码实际上访问sself时变成了如下代码,即访问weakSelf被ARC变为了objc_loadWeakRetained

0000000101cb7bcc         add        x0, sp, #0x8         ;获取sself                       ; CODE XREF=-[MttQuicklinkCustomIconTask getIconImage:]+688
0000000101cb7bd0         bl         imp___stubs__objc_loadWeakRetained
0000000101cb7bd4         mov        x21, x0
0000000101cb7bd8         adrp       x8, #0x103855000
0000000101cb7bdc         ldr        x1, [x8, #0xd18]
0000000101cb7be0         mov        x2, x23
0000000101cb7be4         mov        x3, x20
0000000101cb7be8         bl         imp___stubs__objc_msgSend
0000000101cb7bec         mov        x0, x21
0000000101cb7bf0         bl         imp___stubs__objc_release  ;release sself
0000000101cb7bf4         mov        x0, x23
0000000101cb7bf8         bl         imp___stubs__objc_release
0000000101cb7bfc         add        x0, sp, #0x8
0000000101cb7c00         bl         imp___stubs__objc_destroyWeak

那么造成over-release的可能性有2种,一种是objc_release的释放过程有问题,一种是objc_loadWeakRetained的retain函数有问题;
关键点出现在objc_loadWeakRetained这个方法上.
其实现如下https://opensource.apple.com/source/objc4/objc4-706/runtime/NSObject.mm.auto.html

id
objc_loadWeakRetained(id *location)
{
    id result;

    SideTable *table;
    
 retry:
    result = *location;
    if (!result) return nil;
    
    table = &SideTables()[result];
    
    table->lock();
    if (*location != result) {
        table->unlock();
        goto retry;
    }

    result = weak_read_no_lock(&table->weak_table, location);

    table->unlock();
    return result;
}

在lock()之前会先给result取值,那么当多线程并发时,此时某个线程里释放了location,那么result还是一开始指向location,但接下来走lock,再走weak_read_no_lock,就会无效,因为location的指向已经被置为nil了,那就相当于retain没有走,然后返回了被释放的对象的地址;野指针就产生了;
再接下来使用这个地址干任何事情都是可能crash的;

当然苹果自己在objc注释里也写了类似如下注释

This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)

因此,问题的根本很可能是objc_loadWeakRetained的非多线程安全导致的,再结合发现用户遇到的情况基本都是多线程频繁回调该函数的case,那基本可以断定就是weak的这个特定的锅了;

解决办法也很简单,不适用weak修饰访问了呗;多retain一会儿也无妨;

四、结论

所以:对于容易存在多线程释放并且存在多线程并发访问的对象,不建议使用weak修饰或访问。毕竟weak的底层实现苹果也说明了,并不完全是线程安全,尽量减少这种情况即可;
这也可以解释很多系统库的莫名其妙的over-release问题。

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

推荐阅读更多精彩内容