block的循环引用

block虽然好用,但是里面也有不少坑,最大的坑莫过于循环引用问题。稍不注意,可能就会造成内存泄漏。这节,我将从源码的角度来分析造成循环引用问题的根本原因。并解释变量前加__block,和__weak的区别.

一个循环引用问题

下面我们来看下下面的代码,这样写会出什么问题?

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
    blk_t blk_;
} @end
@implementation MyObject
- (id)init
{
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);}; 
    return self;
}
- (void)dealloc
{
    NSLog(@"dealloc");
    Block_release(blk_);
    [super dealloc];/*在ARC环境中删除该行代码*/
} @end
int main()
{
    id o = [[MyObject alloc] init];
    NSLog(@"%@", o);
    [o release];/*在ARC环境中删除该行代码*/
    return 0;
}

在MRC环境下

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __MyObject__init_block_impl_0 {
    struct __block_impl impl;
    struct __MyObject__init_block_desc_0* Desc;
    MyObject *self;
    __MyObject__init_block_impl_0(void *fp, struct __MyObject__init_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __MyObject__init_block_func_0(struct __MyObject__init_block_impl_0 *__cself) {
    MyObject *self = __cself->self; // bound by copy
    NSLog((NSString*)&__NSConstantStringImpl__var_folders_rv_5xtfn15d3nl5g0z1csq3zch80000gn_T_MyObject_80a1b7_mi_0, self);
}
static void __MyObject__init_block_copy_0(struct __MyObject__init_block_impl_0*dst, struct __MyObject__init_block_impl_0*src){
    _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __MyObject__init_block_dispose_0(struct __MyObject__init_block_impl_0*src) {
    _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __MyObject__init_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __MyObject__init_block_impl_0*, struct __MyObject__init_block_impl_0*);
    void (*dispose)(struct __MyObject__init_block_impl_0*);
} __MyObject__init_block_desc_0_DATA = { 0, sizeof(struct __MyObject__init_block_impl_0), __MyObject__init_block_copy_0, __MyObject__init_block_dispose_0};

static id _I_MyObject_init(MyObject * self, SEL _cmd) {
    self = ((MyObject *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("MyObject")) }, sel_registerName("init"));
    (*(blk_t *)((char *)self + OBJC_IVAR_$_MyObject$blk_)) = (void (*)())&__MyObject__init_block_impl_0((void *)__MyObject__init_block_func_0, &__MyObject__init_block_desc_0_DATA, self, 570425344);
    return self;
}
...

首先我们看下MyObject.m中init函数中的blk_ = ^{NSLog(@"self = %@", self);};。它被转换成了

(*(blk_t *)((char *)self + OBJC_IVAR_$_MyObject$blk_)) = (void (*)())&__MyObject__init_block_impl_0((void *)__MyObject__init_block_func_0, &__MyObject__init_block_desc_0_DATA, self, 570425344);

570425344(1 << 25 | 1 << 29),它在Block_private.h声明,为BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR。这个数被赋值给__block_impl的Flags。给这个是干什么的呢?后面会讲到,先放着不管。

接着我们看下blk_ = ^{NSLog(@"self = %@", self);};,它做了什么呢?blk_是MyObject的成员变量,因为MyObject对象在堆上,blk_也在堆上。^{NSLog(@"self = %@", self);}这个block在栈上生成,因为赋值给堆上的blk_,所以会被隐式地copy到堆上。没错,它就调用了_Block_copy(...)方法。

这有个知识点,什么情况下栈上的block会隐式地进行copy操作。

  • block被赋值到堆上的block变量
  • 在ARC环境下,block被赋值给__strong属性标记的block变量
  • 在ARC环境下,block被当作返回block时

block的copy其实调用的:

/* Copy, or bump refcount, of a block.  If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
    ...
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        ...
        return result;
    }
}

上面的result就是我们要copy的block,我们知道block的flags被赋值为BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR
。来看下copy方法

(*aBlock->descriptor->copy)(result, aBlock);

实现

static void __MyObject__init_block_copy_0(struct __MyObject__init_block_impl_0*dst, struct __MyObject__init_block_impl_0*src){ 
      _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

3BLOCK_FIELD_IS_OBJECT,这个也可以从Block_private.h看到.
__MyObject__init_block_copy_0调用了runtime.c中的

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

flags传进来的为BLOCK_FIELD_IS_OBJECT,执行了

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    ...
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

我们看到_Block_retain_object(object);,它表明被捕获的MyObject对象被retain,也就是被持有了,也就是MyObject对象被持有了两次(一次init
,一次被blk_变量持有),在MyObject对象调用release时,引用计数-1变为1。所以MyObject对象不会调用dealloc方法,而blk_变量是在dealloc释放的,也就是,blk_不会被释放,那blk_持有的MyObject对象也不会被释放。这样便造成了内存泄漏。为了解决这个问题,在变量加个__block
标记。我们再来看下,当加了block后转换的代码:

struct __block_impl {
void isa;
int Flags;
int Reserved;
void FuncPtr;
};
struct __Block_byref_weakSelf_0 {   /*增加*/
    void *__isa;
    __Block_byref_weakSelf_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    typeof (self) weakSelf;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {    /*增加*/
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {    /*增加*/
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __MyObject__init_block_impl_0 {
    struct __block_impl impl;
    struct __MyObject__init_block_desc_0* Desc;
    __Block_byref_weakSelf_0 *weakSelf; // by ref
    __MyObject__init_block_impl_0(void *fp, struct __MyObject__init_block_desc_0 *desc, __Block_byref_weakSelf_0 *_weakSelf, int flags=0) : weakSelf(_weakSelf->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __MyObject__init_block_func_0(struct __MyObject__init_block_impl_0 *__cself) {
    __Block_byref_weakSelf_0 *weakSelf = __cself->weakSelf; // bound by ref

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rv_5xtfn15d3nl5g0z1csq3zch80000gn_T_MyObject_2fcc35_mi_0, (weakSelf->__forwarding->weakSelf));
}
static void __MyObject__init_block_copy_0(struct __MyObject__init_block_impl_0*dst, struct __MyObject__init_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __MyObject__init_block_dispose_0(struct __MyObject__init_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakSelf, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __MyObject__init_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __MyObject__init_block_impl_0*, struct __MyObject__init_block_impl_0*);
    void (*dispose)(struct __MyObject__init_block_impl_0*);
} __MyObject__init_block_desc_0_DATA = { 0, sizeof(struct __MyObject__init_block_impl_0), __MyObject__init_block_copy_0, __MyObject__init_block_dispose_0};

static id _I_MyObject_init(MyObject * self, SEL _cmd) {
    self = ((MyObject *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("MyObject")) }, sel_registerName("init"));
    __attribute__((__blocks__(byref))) __Block_byref_weakSelf_0 weakSelf = {(void*)0,(__Block_byref_weakSelf_0 *)&weakSelf, 33554432, sizeof(__Block_byref_weakSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
    (*(blk_t *)((char *)self + OBJC_IVAR_$_MyObject$blk_)) = (void (*)())&__MyObject__init_block_impl_0((void *)__MyObject__init_block_func_0, &__MyObject__init_block_desc_0_DATA, (__Block_byref_weakSelf_0 *)&weakSelf, 570425344);
    return self;
}

我们只看下不同的地方。和不加__block多了以下部分

struct __Block_byref_weakSelf_0 {
    void *__isa;
    __Block_byref_weakSelf_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    typeof (self) weakSelf;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

持有普通类型变量不同,object的copy需要管理引用计数,比持有普通类型变量多了copy,和dispose函数。等下我们再分析,先往下看。__block typeof(self) weakSelf = self;转化的代码为

attribute((blocks(byref))) Block_byref_weakSelf_0 weakSelf = {(void*)0,(Block_byref_weakSelf_0 )&weakSelf, 33554432, sizeof(Block_byref_weakSelf_0), Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};

去掉强转代码后

__Block_byref_weakSelf_0 weakSelf = {(void*)0,&weakSelf, 33554432, sizeof(__Block_byref_weakSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};

335544321 << 25,即BLOCK_HAS_COPY_DISPOSE
__Block_byref_id_object_copy方法被赋值为__Block_byref_id_object_copy_131
我们再从最开始捋一遍。首先block被拷贝到堆上,这时调用__Block_copy函数。block的flags为570425344BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR.

/* Copy, or bump refcount, of a block.  If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
    ...
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        ...
        return result;
    }
}

aBlock->descriptor->copy调用

static void __MyObject__init_block_copy_0(struct __MyObject__init_block_impl_0*dst, struct __MyObject__init_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 8/*BLOCK_FIELD_IS_BYREF*/);
}

标记下,_Block_object_assign的参数src为含有捕获的对象weakSelf的结构体__Block_byref_weakSelf_0。`

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    ...
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    ...
}

flags值为BLOCK_HAS_COPY_DISPOSE

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;

    ...
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //printf("making copy\n");
    // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        ...
    }
    ...
}

(*src->byref_keep)(copy, src);调用__Block_byref_weakSelf_0__Block_byref_id_object_copy方法,即

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

131BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128)

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    ...
}

当在MRC环境时,直接复制,并不会retain捕获的对象。所以在MRC环境下,__block可以消除循环引用。

在ARC环境下

在ARC环境下,在不加__block的情况下,也会出现循环引用。但是在加上__block后,仍然无法消除循环引用。我们来看下__block typeof(self) weakSelf = self,在ARC下其实为__block __strong typeof(self) weakSelf = self;转换后,如下

struct __Block_byref_weakSelf_0 {
    void *__isa;
    __Block_byref_weakSelf_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    __strong typeof (self) weakSelf;
};

struct __MyObject__init_block_impl_0 {
    struct __block_impl impl;
    struct __MyObject__init_block_desc_0* Desc;
    __Block_byref_weakSelf_0 *weakSelf; // by ref
    __MyObject__init_block_impl_0(void *fp, struct __MyObject__init_block_desc_0 *desc, __Block_byref_weakSelf_0 *_weakSelf, int flags=0) : weakSelf(_weakSelf->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};```
`
weakSelf(_weakSelf->__forwarding)`,即`__strong typeof (self) weakSelf = weakSelf;`
`__strong`为强引用,所以即使加了`__block捕获的对象self仍然会被`retain`。解决方法,加上`__weak`标记,即`__weak typeof(self) weakSelf = self`,这样self就不会被持有了。
### 小节
通过源码分析,我们可以了解使用block引起循环引用的原因,以及解决方案,理解`__block`和`__weak`的作用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容