Block 梳理與疑問
時隔一年凹蜈,再次讀 《Objective-C 高級編程》限寞,看到 block 一章,這一次從頭至尾的跟著編譯了一次仰坦,理清楚了很多之前不理解的地方履植,但是也同時多出了許多疑問。本文是在和學渣裙的朋友們分享以后的梳理筆記悄晃,有問題歡迎指出玫霎,如果能解決最后的幾個小疑問,就更好了妈橄。
環(huán)境信息
macOS 10.12.1
Xcode 8.2.1
iOS 10.12
一個最基本的 block
Block 編譯后庶近,有兩個最為重要的部分,impl 結(jié)構(gòu)體 與 desc 結(jié)構(gòu)體指針眷蚓。我們從最為簡單基礎(chǔ)的開始:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 定義一個參數(shù)列表與返回值均為空的 block
dispatch_block_t block = ^{
// 僅輸出一句話
NSLog(@"123");
};
// 調(diào)用
block();
}
return 0;
}
使用 clang -rewrite-objc xxx.m 命令鼻种,編譯后(已刪除一些影響閱讀的字符,用 xxx 代替):
// block 結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl; // 實現(xiàn)
struct __main_block_desc_0* Desc; // 描述
// 在定義 block 時沙热,所調(diào)用的 block 初始化方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // block 的類型(之后會談到)
impl.Flags = flags;
impl.FuncPtr = fp; // block 實現(xiàn)編譯后的函數(shù)指針
Desc = desc; // 描述信息
}
};
// block 的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block 所占的內(nèi)存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // block 描述的初始化方法普舆,可以看出這里的大小計算恬口,僅僅是進行了 sizeof
// block 實現(xiàn)編譯過后的函數(shù)
// 即在 block 初始化方法中,賦值給 impl.FuncPtr 的函數(shù)指針
// 參數(shù) cself 是 __main_block_impl_0 類型沼侣,即與 block 類型相同祖能,其實這里的參數(shù),本身就是 block 自己
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 輸出
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_main_c44db5_mi_0);
}
// main 函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// block 的定義
// 可以看出蛾洛,在 block 定義的時候养铸,就調(diào)用了 __main_block_impl_0,即 block 的構(gòu)造方法
// 傳的參數(shù)分別為 __main_block_func_0轧膘,即 block 對應(yīng)的編譯后的實現(xiàn)函數(shù)
// __main_block_desc_0_DATA钞螟,即 block 描述
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// block 的調(diào)用
// 這里也可以看出,block 的調(diào)用即是調(diào)用了谎碍,初始化時拿到的 FuncPtr 函數(shù)指針
// FuncPtr 函數(shù)有一個參數(shù)鳞滨,即傳入的 block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
block 內(nèi)存結(jié)構(gòu)
通過編譯后得到的 block 結(jié)構(gòu)體,能大致看出蟆淀,在沒有引用外部變量的 block 是這樣的:
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
此時的內(nèi)存結(jié)構(gòu)如下:
isa 指針
在 block 調(diào)用構(gòu)造方法時拯啦,編譯器已經(jīng)自動給 isa 指針賦了初值。我們知道熔任,isa 指針其實很形象褒链,就稱作 is a,在 OC 中表達了對象是什么類型疑苔,類所屬哪個元類甫匹,其實 block 也是對象,所以它的 isa 指針也是說明它是什么的惦费。如果直接打印 block兵迅,則可看到以下三種情況:
<NSStackBlock: 0x1000010c0>,存儲在棧上的 block
<NSMallocBlock: 0x1000010c0>薪贫,存儲在堆上的 block
<NSGlobalBlock: 0x1000010c0>喷兼,存儲在全局區(qū)的 block
但是你會發(fā)現(xiàn),如果直接打印上面我們所寫的 block后雷,輸出的是 NSGlobalBlock 類型,而我們看到的編譯代碼吠各,明明是 stack 的臀突。這是因為 block 的存儲區(qū)域,與定義在什么位置贾漏、是否引用外部變量候学、是否作為范圍值、是被哪種類型的變量所接收等等情況相關(guān)纵散,這個會在下一小節(jié)談到梳码。
引用外部變量的 block
之前介紹了一個空(并未引用變量)的 block隐圾,下面來看一個稍微復(fù)雜一點的:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 定義局部變量 a
int a = 10;
// 定義 block
dispatch_block_t block = ^{
// 輸出 a 變量
NSLog(@"%d", a);
};
// 調(diào)用 block
block();
}
return 0;
}
在學習 block 的基礎(chǔ)知識時,就知道掰茶,此時如果在 block 定義之后暇藏,去修改 a 的值,block 中的輸出依然不會改變濒蒋,我們來看一下為什么盐碱。
編譯文件:
// 下面僅標注了有變化的變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 在 block 結(jié)構(gòu)體中,多了一個名為 a 的變量
// block 構(gòu)造方法也多了一個 _a 參數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 描述依然沒有變沪伙,size 是直接計算的 __main_block_impl_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 在 block 的實現(xiàn)函數(shù)中瓮顽,訪問 block 結(jié)構(gòu)體中的 a 變量,并且編譯器在此還說明了是 bound by copy围橡,即值拷貝
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, a);
}
從編譯代碼可以看出暖混,在 block 定義時,就傳入了 block 內(nèi)部需要用到的 a 變量的值翁授,而并不是引用拣播,所以即使在 block 定義之后,a 變量怎么變黔漂,之前 block 所有的 a 的瞬時值诫尽,是沒有變化的。
此時炬守,block 的內(nèi)存結(jié)構(gòu)為:
多出了捕獲的變量 a 的存儲空間牧嫉,并且,捕獲的變量會接在 Desc 內(nèi)存后面减途。
被 __block 修飾的外部變量
如果沒有 __block 修飾酣藻,除了在 block 定義之后,就不能拿到變量最新的值以外鳍置,我們還不能對變量進行重新賦值(如果是堆上的內(nèi)存辽剧,就是改變地址,即 NSMutableArray 是可以 addObject 的税产,只是不能 array = @[])怕轿。那么,想要解決這兩個問題辟拷,我們就需要引入 __block 修飾符:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 定義變量 a撞羽,并使用 __block 修飾
__block int a = 10;
dispatch_block_t block = ^{
// 輸出 a
NSLog(@"%d", a); // 輸出 100
// 在 block 內(nèi)部對 a 重新賦值
a = 50;
};
// 在 block 定義后,對 a 重新賦值
a = 100;
// 調(diào)用 block
block();
// 輸出 a
NSLog(@"%d", a); // 輸出 50
}
return 0;
}
這一次衫冻,編譯后的代碼變得很復(fù)雜了:
// block 結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 比起沒有被 __block 修飾的變量(編譯后诀紊,block 中是 int a),這里 block 卻不是簡單的拿到 a 地址隅俘,即 int *a邻奠,而是一個類型為 __Block_byref_a_0 的結(jié)構(gòu)體指針
__Block_byref_a_0 *a; // by ref
// 構(gòu)造方法多出的參數(shù)也變成了笤喳,__Block_byref_a_0 結(jié)構(gòu)體指針,并且 a 的值是 a->__forwarding碌宴,這個 __forwarding 指針的作用杀狡,會在之后介紹
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// a 變量的結(jié)構(gòu)體定義
struct __Block_byref_a_0 {
void *__isa; // isa 指針
__Block_byref_a_0 *__forwarding; // 類型與 a 變量一模一樣的 __forwarding 結(jié)構(gòu)體指針
int __flags;
int __size;
int a; // a 真正的值
};
// block 描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// 這是比起以前,多出的兩個函數(shù)指針唧喉,一個 copy捣卤,一個 dispose
// 這也是 block 中尤為重要的兩個函數(shù)
// copy 負責將 block 復(fù)制到堆
// dispose 負責在 block 釋放時,釋放 block 所持有的內(nèi)存
void (copy)(struct __main_block_impl_0, struct __main_block_impl_0);
void (dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// block 實現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 訪問到 block 中的 a 結(jié)構(gòu)體指針變量
__Block_byref_a_0 *a = __cself->a; // bound by ref
// 輸出
// 可以看到八孝,這里訪問的 a 的值董朝,是通過 __forwarding 指針訪問的,包括之后的賦值干跛,也是用的 __forwarding
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_0, (a->__forwarding->a));
// 將 a 的值重新賦為 50
(a->__forwarding->a) = 50;
}
// copy 函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {
// 使用 _Block_object_assign 函數(shù)進行拷貝
_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}
// dispose 函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0src) {
_Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 初始化 a 的結(jié)構(gòu)體變量
// 傳入的參數(shù)分別是:
// isa: void *0;
// __forwarding: &a子姜,即 a 結(jié)構(gòu)體變量的地址
// __flags: 0
// __size: sizeof(結(jié)構(gòu)體)
// a: 10,即 a 的值
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
// block 定義
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// 對 a 變量賦值
(a.__forwarding->a) = 100;
// block 的調(diào)用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 輸出 a
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_1, (a.__forwarding->a));
}
return 0;
}
經(jīng)過了這一坨編譯后代碼的理解楼入,現(xiàn)在已經(jīng)腦子已經(jīng)是一團糨糊了哥捕,莫名多出的結(jié)構(gòu)體、__forwarding 指針嘉熊,copy 與 dispose 函數(shù)無一不在提高理解的門檻遥赚。代碼都看得懂,但是就是不知道這樣做的目的阐肤,下面我們便來將多出的東西凫佛,重新整理一次,然后注意理解:
現(xiàn)在的 block 內(nèi)存結(jié)構(gòu)是怎樣的孕惜?
為什么 a 被 __block 修飾以后愧薛,就變成了 __Block_byref_a_0 結(jié)構(gòu)體?
多出的 __forwarding 指針是什么衫画?
為什么之后無論是在 block 內(nèi)部毫炉,還是在 block 外部,訪問 a 都變成了 a->__forwarding->a 削罩?
既然定義了 copy 與 dispose 函數(shù)瞄勾,為什么沒有看到顯式調(diào)用?如果是隱式調(diào)用弥激,那么調(diào)用時機是什么時候进陡?
多個 block 對 __block int a = 10; 進行使用,指針會怎樣指向秆撮?
當前的 block 內(nèi)存結(jié)構(gòu)
從 main 函數(shù)中 _Block_byref_a_0 的初始化可以看出,給 __forwarding 指針賦的值就是 (__Block_byref_a_0 *)&a换况,所以 __forwarding 指針是同樣是指向 a 結(jié)構(gòu)體變量本身的职辨。
為什么在 block 中的變量盗蟆,超出作用域還能使用
在全局區(qū)或者是棧上的 block,我們并不能控制它的釋放時機舒裤,但是如果 block 在堆中喳资,就可以由我們來控制了。所以腾供,大多數(shù)情況下仆邓,比如將 block 作為回調(diào)方法等時候,block 一般都是在堆上的伴鳖。
那么节值,block 是如何拷貝到堆上的呢?這就和 copy 函數(shù)有關(guān)了榜聂。在 ARC 環(huán)境下搞疗,如果將 block 聲明為:
@property (copy) block;
@property (strong) block;
在賦值時,其實都會調(diào)用 Block_copy() 函數(shù)须肆,將棧上的 block 拷貝到堆中匿乃,此時,block 中所持有的變量就都在堆中了豌汇,我們通過管理 block 的生命周期幢炸,就能間接管理到 block 持有的變量的生命周期。
block 的 copy 時機
那么 block 何時會 copy 到堆上呢拒贱?是顯式宛徊,還是隱式?
顯式
在作為屬性定義時柜思,用 copy 和 strong 修飾岩调;
手動調(diào)用 [block copy];
隱式
賦值給 __strong 修飾的變量時赡盘。因為 ARC 下号枕,__strong 是缺省值,所以只要不是顯式標記了 __unsafe_unretained 或 __weak陨享,block 均會被拷貝到堆上葱淳;
作為函數(shù)返回值;
含有 usingBlock 的 Cocoa 框架中的方法抛姑,如枚舉器赞厕;
GCD 的 block。
__forwarding 指針存在的意義
在閱讀編譯代碼時可以發(fā)現(xiàn)定硝,block 在讀寫被 __block 標記的變量時皿桑,均使用 var->__forwarding->var 來訪問。var 是指針能理解,因為它肯定是對臨時變量進行地址引用诲侮,要不然也不能獲得最新的值镀虐。但是為什么要在中間加一個 __forwarding 呢?而且 __forwarding 指針還是指向的自己沟绪。
來看一個例子(ARC 下):
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block int a = 10;
NSLog(@"1. block 定義之前 a 的地址 : %p", &a);
dispatch_block_t __unsafe_unretained block = ^{
a = 100;
NSLog(@"2. 調(diào)用 block 時 a 的地址 : %p", &a);
};
NSLog(@"3. block 定義之后 a 的地址 : %p", &a);
dispatch_block_t heapBlock = block;
NSLog(@"4. block 拷貝到堆上 a 的地址 : %p", &a);
block();
}
return 0;
}
上面的例子中刮便,一共輸出了四次 a 的地址。其中绽慈,1 和 3 的地址是一樣的恨旱,2 和 4 的地址是一樣。
這里我還對 block 特地標記了 __unsafe_unretained坝疼,防止在定義賦值的時候搜贤,就拷貝到堆。而這之后的 heapBlock 則是因為被 __strong 修飾所以將 block 拷貝到了堆裙士。
在 1入客、3 輸出的時候,a 還在棧上腿椎,此時的 block 內(nèi)存為:
而在 block 拷貝到堆上以后桌硫, __forwarding 指針則指向堆上的 a 結(jié)構(gòu)體,所以啃炸,內(nèi)存變成了這樣:
這樣就保證了棧上和堆上的 block铆隘,都能訪問到同一個 a 變量,這也是 __forwarding 指針的作用南用。
block 的存儲區(qū)域
之前談到 block 根據(jù)存儲位置不同膀钠,可分為三種,堆裹虫、棧肿嘲、全局區(qū)。那么這三種 block 是怎樣的呢筑公?
NSStackBlock:block 被定義為臨時變量雳窟,并且引用了外部變量;
NSMallocBlock:調(diào)用了 copy 函數(shù)匣屡,被拷貝到堆上的 block封救;
NSGlobalBlock:定義為全局變量,或者臨時變量但是沒有引用外部變量的 block捣作。
多個 block 對 __block 變量的引用
在 block 引用使用 __block 修飾的外部變量時誉结,編譯器去針對這個外部變量生成了結(jié)構(gòu)體,比如我們上面談到的 __Block_byref_a_0 結(jié)構(gòu)體券躁。
之所以這樣做惩坑,也是為了能在多個 block 引用時掉盅,能夠給對 __Block_byref_a_0 進行復(fù)用。所以以舒,當多個 block 引用該變量時怔接,并不會重復(fù)生成結(jié)構(gòu)體,而是對該結(jié)構(gòu)體內(nèi)存進行持有稀轨,在 block 銷毀,調(diào)用 dispose 時岸军,對內(nèi)存進行釋放奋刽。
循環(huán)引用
OC 中的循環(huán)引用是一個老生常談的問題,其中最容易出現(xiàn)循環(huán)引用的地方艰赞,就是 block佣谐。都知道,出現(xiàn)循環(huán)引用的原因方妖,是因為兩個變量的相互持有狭魂,導(dǎo)致誰也無法釋放。斷開循環(huán)引用鏈党觅,最常見的方式是:
在源頭斷開:一方不持有另一方雌澄;
通過置空斷開:在已經(jīng)對象使用完畢,需要釋放的時候杯瞻,將一方置空镐牺。
根據(jù)這兩種解決方案,block 解決循環(huán)引用對應(yīng)著兩種方式:
使用 __weak 或者 __unsafe_unretained 修飾 block 內(nèi)部要用到的變量魁莉。
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf);
};
self.block();
使用 __block 修飾變量睬涧,然后在 block 調(diào)用完畢后,在 block 內(nèi)部對變量置空旗唁。
__block typeof(self) blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf);
blockSelf = nil;
};
self.block();
使用第二種方式有一個弊端畦浓,就是必須要保證 block 會調(diào)用,這樣才有機會斷開循環(huán)引用检疫,否則無法解決問題讶请。當然,也有優(yōu)點电谣,即可以控制另一方的釋放時機秽梅,保證不調(diào)用,就不會釋放。
__weak 與 __strong
通常我們能看到以下寫法:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
self.block();
__weak 的作用我們剛才已經(jīng)提到了,但是在 block 內(nèi)部又使用 __strong 標記是為什么汤徽?這樣會造成循環(huán)引用嗎口糕?我們來看看 block 實現(xiàn)編譯過后的代碼:
static void __Test__init_block_func_0(struct __Test__init_block_impl_0 *__cself) {
// 這里變成了值拷貝,而不是指針引用
typeof (self) weakSelf = __cself->weakSelf; // bound by copy
// 雖然是 strong 的空郊,但是是在 block 調(diào)用時谷饿,才將 self 的值拷貝賦值給臨時變量 weakSelf未荒,之后被 strongSelf 引用
// 根據(jù) ARC 的規(guī)則荧降,使用 __strong 修飾的變量接箫,出作用域以后,會插入 release 語句朵诫,所以在 block 實現(xiàn)結(jié)束后辛友,strongSelf 會釋放,并不會造成循環(huán)引用
__attribute__((objc_ownership(strong))) Test * strongSelf = weakSelf;
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, strongSelf);
}
也是因為 ARC 對 __strong 修飾的變量剪返,出作用域才插入 release 的機制废累,我們可以知道,之所以在 block 內(nèi)部使用 __strong 修飾變量脱盲,是因為防止在 block 執(zhí)行過程中邑滨,變量被釋放的情況。
在 block 中調(diào)用的方法含有 self钱反,是否會造成循環(huán)引用
再看下面這段代碼:
(void)testBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
// 在 block 實現(xiàn)中掖看,調(diào)用了 run 方法,而 run 方法中面哥,又用到了 self
[strongSelf run];
};
self.block();
}(void)run {
NSLog(@"%@", self);
}
之前有人問到這個問題哎壳,說如果 block 中調(diào)用的方法又用到了 self,會造成循環(huán)引用嗎尚卫?想想如果會造成耳峦,那豈不是很可怕,好像一直這樣寫焕毫,都沒什么問題蹲坷。那這是為什么呢?
別忘了 OC 是消息機制邑飒,發(fā)送完消息之后就不管了循签,所以,并不影響消息的實現(xiàn)疙咸。
疑問
雖然能將 block 編譯出來看到代碼县匠,但是還是有很多疑問的,希望大家能解答一下撒轮。
ARC 與 MRC 下乞旦,有無 __block 標識,對 block 持有對象的影響
其實這里是四種狀態(tài):
ARC + 無 __block
MRC + 無 __block
ARC + 有 __block
MRC + 有 __block
首先來看問題 1题山、2:
-
(instancetype)init {
self = [super init];
if (self) {// 定義一個可變數(shù)組 arr NSMutableArray *arr = [NSMutableArray new]; // 輸出 retainCount NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 // 為減少隱式的 __strong 造成拷貝到堆的影響兰粉,所以使用 __unsafe_unretained 修飾 __unsafe_unretained dispatch_block_t block = ^{ NSLog(@"%@", arr); // 輸出調(diào)用 block 時,arr 的 retainCount NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 }; // 在定義完 block 后顶瞳,arr 的 retainCount NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 2; MRC: 1 // 顯示拷貝到堆 self.block = block; // 在 block 拷貝到堆以后玖姑,arr 的 retainCount NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 3; MRC: 2 // 如果是 MRC愕秫,則手動 release [arr release];
}
return self;
}
// 調(diào)用 block
- (void)run {
self.block();
}
可以看到,同樣沒有使用 __block 修飾焰络,ARC 在 block 定義完以后戴甩,arr 的 retainCount 要比 MRC 下多 1,這是因為在 block 的結(jié)構(gòu)體中闪彼,所定義的 NSMutableArray *arr甜孤,默認的缺省值是 __strong,而導(dǎo)致的持有畏腕,而 MRC 下课蔬,缺省值不是 __strong 造成的。
再來看問題 2郊尝、4,還是借助上面的例子战惊,只是在 arr 定義時流昏,在前面使用 __block 進行修飾,但這一次的 retainCount 卻大為不同:
-
(instancetype)init {
self = [super init];
if (self) {__block NSMutableArray *arr = [NSMutableArray new]; NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 __unsafe_unretained dispatch_block_t block = ^{ NSLog(@"%@", arr); NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 }; NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 self.block = block; NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1 [arr release];
}
return self;
} (void)run {
self.block();
}
這一次吞获,我們發(fā)現(xiàn)况凉,無論是 ARC,還是 MRC各拷,arr 的 retainCount 始終為 1刁绒,在查閱資料后,找到這樣一句話:
在 MRC 下烤黍,__block 說明符可被用來避免循環(huán)引用知市,是因為當 block 從棧復(fù)制到堆上時,如果變量被 __block 修飾速蕊,則不會再次 retain嫂丙,如果沒有被 __block 修飾,則會被 retain规哲。
但是跟啤,從上面的代碼輸出來看,ARC 和 MRC唉锌,block 是否拷貝到堆上隅肥,都沒有再次對變量進行持有,retainCount 始終為 1袄简,所以腥放,到這里我遇到幾個不太理解的地方:
__block 修飾符不再持有對象,僅僅是在 MRC 下有效绿语,還是 ARC 與 MRC 下效果是相同的捉片?
如果效果是相同的平痰,為什么 __block 不能解決 ARC 下的循環(huán)引用問題?
不能解決 ARC 下的循環(huán)引用問題伍纫,是否是因為 ARC 下宗雇,arr 定義時,缺省值是 __strong 導(dǎo)致的莹规?
在 ARC 下赔蒲,變量出作用域,編譯器插入 release良漱,為什么 arr 的 retainCount 是 1舞虱,經(jīng)過一次 release 以后,并未出現(xiàn)問題母市,而在 MRC 下矾兜,在 block 調(diào)用的時候,就會出現(xiàn) crash患久?