block的迴圈引用究竟是什麼鬼?
block的迴圈引用到底是怎麼回事?
我們先來看一段程式碼:
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
simpleblock_ = ^{
NSLog(@"self is %@",self);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
我們肯定不加思索的說這樣造成了迴圈引用,因為self持有了block,block又持有了self,所以這個類不會得到釋放,所以就會出現迴圈引用,造成記憶體洩露。可是,為什麼這樣呢,self持有block很好理解,可是block又是怎麼持有了self了呢?下面做一下深入分析。
記得以前還沒有使用ARC的時候,也就是使用MRC的時候,我們為了避免這種迴圈引用的情況,都是通過加__block來避免這種問題的,就是:
- (id)init
{
self = [super init];
if (self) {
__block typeof(self) blockSelf = self;
simpleblock_ = ^{
NSLog(@"self is %@",self);
};
}
return self;
}
後來使用ARC的時候,發現加__block不能避免這種問題了,改成使用__weak了,那麼究竟為什麼會這樣,MRC和ARC究竟有什麼不同,發生了什麼呢。我們分別探究一下。
1.MRC的情況
我們在MRC的情況下,使用以下程式碼:
MRC下不加任何修飾:
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
simpleblock_ = ^{
NSLog(@"self is %@",self);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
使用clang進行反編譯後,得到如下內容:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
BlockCase *self;
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, BlockCase *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockCase__init_block_func_0(struct __BlockCase__init_block_impl_0 *__cself) {
BlockCase *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_c89a3d_mi_0,self);
}
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __BlockCase__init_block_dispose_0(struct __BlockCase__init_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __BlockCase__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockCase__init_block_impl_0*, struct __BlockCase__init_block_impl_0*);
void (*dispose)(struct __BlockCase__init_block_impl_0*);
} __BlockCase__init_block_desc_0_DATA = { 0, sizeof(struct __BlockCase__init_block_impl_0), __BlockCase__init_block_copy_0, __BlockCase__init_block_dispose_0};
static id _I_BlockCase_init(BlockCase * self, SEL _cmd) {
self = ((BlockCase *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockCase"))}, sel_registerName("init"));
if (self) {
(*(simpleBlock *)((char *)self + OBJC_IVAR_$_BlockCase$simpleblock_)) = ((void (*)())&__BlockCase__init_block_impl_0((void *)__BlockCase__init_block_func_0, &__BlockCase__init_block_desc_0_DATA, self, 570425344));
}
return self;
}
int main()
{
id myCase = ((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockCase"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_c89a3d_mi_1, myCase);
return 0;
}
之前在block深究這篇文章中,已經解釋了一些基本的東西,這裡就不一一解釋,我們看到
simpleblock_ = ^{
NSLog(@"self is %@",self);
};
這裡發生了什麼呢?simpleblock_是BlockCase的成員變數,因為BlockCase物件在堆上,simpleblock_也在堆上。^{NSLog(@”self = %@”, self);}這個block在棧上生成,因為賦值給堆上的simpleblock_,所以會被copy到堆上。那麼你可能會問還有沒有其他情況下,會把棧上的block複製到堆上呢?答案是有。
- 呼叫block的copy例項方法的時候
- block作為函式的返回值返回的時候
- 將block賦值給附有__strong修飾符id型別的類或者block型別成員變數的時候
- 在方法名中含有usingBlock和Cocoa框架方法或Grand Central Dispatch的API中傳遞block的時候
在上述例子程式碼中,我們直接使用self,麼有對self進行任何修飾,但是在反編譯的程式碼中,依然出現了輔助函式:
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
之前在 block深究這篇文章中,我們知道block在複製到堆的時候會使用__BlockCase__init_block_copy_0輔助函式把自動變數從棧區賦值到堆裡面,但是:
id myCase = [[BlockCase alloc] init];
也就是myCase已經儲存在堆裡了,那麼self已經在堆裡面了,又是怎麼copy的呢?
我們看一下原始碼:
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
void _Block_object_assign(void *destAddr, const void *object, const int 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) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
}
我們看到_Block_object_assign呼叫的時候傳入第一個引數為self地址,第二個引數為物件self,第三個引數是型別 3/BLOCK_FIELD_IS_OBJECT/,匹配對對應的條件,執行程式碼為:
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
這裡出現了_Block_retain_object(object);它表明被捕獲的BlockCase物件被retain,也就是被持有了,也就是MyCase物件被持有了兩次(一次init,一次被blk變數持有),在BlockCase物件呼叫release時,引用計數-1變為1。所以BlockCase物件不會呼叫dealloc方法,而block變數是在dealloc釋放的,也就是,block不會被釋放,那block持有的BlockCase物件也不會被釋放。這樣便造成了記憶體洩漏。
_Block_assign(src->forwarding, (void **)destp);這句話就是普通的賦值,裡面最底層就*destptr = value;這句表示式。
那麼我們在MRC下對self加入__block修飾之後呢?
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
__block typeof(self) blockSelf = self;
simpleblock_ = ^{
NSLog(@"self is %@",blockSelf);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
進行反編譯,檢視原始碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_blockSelf_0 {
void *__isa;
__Block_byref_blockSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
typeof (self) blockSelf;
};
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 __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
__Block_byref_blockSelf_0 *blockSelf; // by ref
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, __Block_byref_blockSelf_0 *_blockSelf, int flags=0) : blockSelf(_blockSelf->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockCase__init_block_func_0(struct __BlockCase__init_block_impl_0 *__cself) {
__Block_byref_blockSelf_0 *blockSelf = __cself->blockSelf; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_5642dd_mi_0,(blockSelf->__forwarding->blockSelf));
}
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->blockSelf, (void*)src->blockSelf, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __BlockCase__init_block_dispose_0(struct __BlockCase__init_block_impl_0*src) {_Block_object_dispose((void*)src->blockSelf, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __BlockCase__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockCase__init_block_impl_0*, struct __BlockCase__init_block_impl_0*);
void (*dispose)(struct __BlockCase__init_block_impl_0*);
} __BlockCase__init_block_desc_0_DATA = { 0, sizeof(struct __BlockCase__init_block_impl_0), __BlockCase__init_block_copy_0, __BlockCase__init_block_dispose_0};
static id _I_BlockCase_init(BlockCase * self, SEL _cmd) {
self = ((BlockCase *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockCase"))}, sel_registerName("init"));
if (self) {
__attribute__((__blocks__(byref))) __Block_byref_blockSelf_0 blockSelf = {(void*)0,(__Block_byref_blockSelf_0 *)&blockSelf, 33554432, sizeof(__Block_byref_blockSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
(*(simpleBlock *)((char *)self + OBJC_IVAR_$_BlockCase$simpleblock_)) = ((void (*)())&__BlockCase__init_block_impl_0((void *)__BlockCase__init_block_func_0, &__BlockCase__init_block_desc_0_DATA, (__Block_byref_blockSelf_0 *)&blockSelf, 570425344));
}
return self;
}
int main()
{
id myCase = ((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockCase"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_5642dd_mi_1, myCase);
return 0;
}
我們看到比不加修飾符,多了:
struct __Block_byref_blockSelf_0 {
void *__isa;
__Block_byref_blockSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
typeof (self) blockSelf;
};
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);
}
上一篇block深究裡面,我們已經解釋過__Block_byref,這裡的__Block_byref_blockSelf_0多了兩個東西void (__Block_byref_id_object_copy)(void, void*);和void (__Block_byref_id_object_dispose)(void);。以及外面多了兩個方法__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131,多的這些都是幹什麼的呢是幹什麼的呢?我們一會兒就會提到。
我們繼續看看輔助函式__BlockCase__init_block_copy_0
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->blockSelf, (void*)src->blockSelf, 8/*BLOCK_FIELD_IS_BYREF*/);}
我們看到_Block_object_assign方法傳入的引數,第三個型別為8/BLOCK_FIELD_IS_BYREF/,我們檢視_Block_object_assign原始碼:
void _Block_object_assign(void *destAddr, const void *object, const int 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) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
}
根據程式碼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);
}
我們發現這裡沒有對傳入的物件做引用計數做相關操作,而是直接執行了_Block_byref_assign_copy方法,那麼_Block_byref_assign_copy方法的原始碼又是如何呢,我們看一下:
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) {
// 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_blockSelf_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);
}
131為BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128),我們看看_Block_object_assign的實現:
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);
}
}
...
}
根據flags的匹配,最終的執行程式碼是:
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
我們之前說_Block_assign裡面就是一個賦值,沒有其他操作,所以得出結論,加入__block後,沒有對self做引用計數的相關操作,也就是block不會持有MyCase物件。所以也就不會造成迴圈引用。
1.ARC的情況
ARC下不加修飾
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
simpleblock_ = ^{
NSLog(@"self is %@",self);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
使用clang進行反編譯後,得到如下內容:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
BlockCase *self;
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, BlockCase *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockCase__init_block_func_0(struct __BlockCase__init_block_impl_0 *__cself) {
BlockCase *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_29fa49_mi_0,self);
}
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __BlockCase__init_block_dispose_0(struct __BlockCase__init_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __BlockCase__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockCase__init_block_impl_0*, struct __BlockCase__init_block_impl_0*);
void (*dispose)(struct __BlockCase__init_block_impl_0*);
} __BlockCase__init_block_desc_0_DATA = { 0, sizeof(struct __BlockCase__init_block_impl_0), __BlockCase__init_block_copy_0, __BlockCase__init_block_dispose_0};
static id _I_BlockCase_init(BlockCase * self, SEL _cmd) {
self = ((BlockCase *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockCase"))}, sel_registerName("init"));
if (self) {
(*(simpleBlock *)((char *)self + OBJC_IVAR_$_BlockCase$simpleblock_)) = ((void (*)())&__BlockCase__init_block_impl_0((void *)__BlockCase__init_block_func_0, &__BlockCase__init_block_desc_0_DATA, self, 570425344));
}
return self;
}
int main()
{
id myCase = ((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockCase"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_29fa49_mi_1, myCase);
return 0;
}
這裡反編譯得到的程式碼和之前在MRC相同情況下反編譯的一樣,不做多餘解釋,這裡也造成了迴圈引用。
那麼在ARC下加入__block修飾呢?寫如下程式碼:
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
__block typeof(self) blockSelf = self;
simpleblock_ = ^{
NSLog(@"self is %@",blockSelf);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
在ARC情況下,我們知道一般的指標變數預設就是strong型別的,因此一般我們對於strong變數不加__strong修飾,strong是在ARC後引入的關鍵字,strong類似於retain,引用時候會引用計數+1,以下兩行程式碼是等價的:
BlockCase *case1 = [[BlockCase alloc] init];
__strong BlockCase *case1 = [[BlockCase alloc] init];
也就是說:
__block typeof(self) blockSelf = self;
等價於
__block __strong typeof(self) blockSelf = self;
__strong為強引用,所以即使加了__block捕獲的物件self仍然會被retain。所以還是會造成迴圈引用,產生記憶體洩露。
那麼我們使用__weak修飾會怎麼樣呢?
我們使用如下程式碼:
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
simpleblock_ = ^{
NSLog(@"self is %@",weakSelf);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
進行反編譯得到:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
__weak typeof (self) weakSelf;
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, __weak typeof (self) _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockCase__init_block_func_0(struct __BlockCase__init_block_impl_0 *__cself) {
__weak typeof (self) weakSelf = __cself->weakSelf; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_fbf628_mi_0,weakSelf);
}
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __BlockCase__init_block_dispose_0(struct __BlockCase__init_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __BlockCase__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockCase__init_block_impl_0*, struct __BlockCase__init_block_impl_0*);
void (*dispose)(struct __BlockCase__init_block_impl_0*);
} __BlockCase__init_block_desc_0_DATA = { 0, sizeof(struct __BlockCase__init_block_impl_0), __BlockCase__init_block_copy_0, __BlockCase__init_block_dispose_0};
static id _I_BlockCase_init(BlockCase * self, SEL _cmd) {
self = ((BlockCase *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockCase"))}, sel_registerName("init"));
if (self) {
__attribute__((objc_gc(weak))) typeof(self) weakSelf = self;
(*(simpleBlock *)((char *)self + OBJC_IVAR_$_BlockCase$simpleblock_)) = ((void (*)())&__BlockCase__init_block_impl_0((void *)__BlockCase__init_block_func_0, &__BlockCase__init_block_desc_0_DATA, weakSelf, 570425344));
}
return self;
}
int main()
{
id myCase = ((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockCase"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_fbf628_mi_1, myCase);
return 0;
}
我們發現了一個地方和之前有不同:
struct __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
__weak typeof (self) weakSelf;
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, __weak typeof (self) _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這裡block裡面的weakSelf變數竟然使用了__weak進行修飾,也就是說是弱引用,不讓改變例項的引用計數,也就是不會持有self。這樣就做到消除迴圈引用了。
綜上所述,我們知道了MRC下面消除迴圈引用使用__block, ARC下面使用__weak,以及為什麼ARC下面不能使用__block消除迴圈引用的原因。
看到這裡,也許你會問一個問題,在之前我們寫一些程式碼的時候,好像沒有用到self,也會產生迴圈引用,例如以下程式碼:
@interface BlockCase() {
simpleBlock simpleblock_;
NSString *_name;
}
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
simpleblock_ = ^{
NSLog(@"name is %@",_name);
};
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
我們看到block裡面用到了BlockCase的一個成員變數,直接使用_name的方式使用,根據我們的經驗,我們知道這樣也會產生迴圈引用,為什麼呢,看一下原始碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __BlockCase__init_block_impl_0 {
struct __block_impl impl;
struct __BlockCase__init_block_desc_0* Desc;
BlockCase *self;
__BlockCase__init_block_impl_0(void *fp, struct __BlockCase__init_block_desc_0 *desc, BlockCase *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockCase__init_block_func_0(struct __BlockCase__init_block_impl_0 *__cself) {
BlockCase *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_bbfcaf_mi_0,(*(NSString **)((char *)self + OBJC_IVAR_$_BlockCase$_name)));
}
static void __BlockCase__init_block_copy_0(struct __BlockCase__init_block_impl_0*dst, struct __BlockCase__init_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __BlockCase__init_block_dispose_0(struct __BlockCase__init_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __BlockCase__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockCase__init_block_impl_0*, struct __BlockCase__init_block_impl_0*);
void (*dispose)(struct __BlockCase__init_block_impl_0*);
} __BlockCase__init_block_desc_0_DATA = { 0, sizeof(struct __BlockCase__init_block_impl_0), __BlockCase__init_block_copy_0, __BlockCase__init_block_dispose_0};
static id _I_BlockCase_init(BlockCase * self, SEL _cmd) {
self = ((BlockCase *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockCase"))}, sel_registerName("init"));
if (self) {
(*(simpleBlock *)((char *)self + OBJC_IVAR_$_BlockCase$simpleblock_)) = ((void (*)())&__BlockCase__init_block_impl_0((void *)__BlockCase__init_block_func_0, &__BlockCase__init_block_desc_0_DATA, self, 570425344));
}
return self;
}
int main()
{
id myCase = ((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockCase *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockCase"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s5_77zrry7j3b570h0tq36z405c0000gn_T_BlockCase_bbfcaf_mi_1, myCase);
return 0;
}
在結構體__BlockCase__init_block_impl_0裡面,我們發現竟然出現了 BlockCase *self; 也就是說雖然我們直接使用了_name,看似沒有用到self,但是原始碼裡面還是引用了self,所以這樣就造成了迴圈引用。
下面再來看看直接使用self.name呢,使用如下程式碼:
typedef void(^simpleBlock)();
@interface BlockCase() {
simpleBlock simpleblock_;
NSString *_name;
}
@property (nonatomic, strong) NSString *name;
@end
@implementation BlockCase
- (id)init
{
self = [super init];
if (self) {
simpleblock_ = ^{
NSLog(@"name is %@",self.name);
}
}
return self;
}
int main()
{
id myCase = [[BlockCase alloc] init];
NSLog(@"%@", myCase);
return 0;
}
@end
經過反編譯,發現發編譯後的程式碼和使用_name效果是一樣的,都會引用迴圈引用。所以我們得出當在block裡面引用了物件的成員變數的時候,也會和self造成迴圈引用,所以要加__weak修飾來避免迴圈引用,也就是:
- (id)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
simpleblock_ = ^{
NSLog(@"name is %@",weakSelf.name);
};
}
return self;
}