最近在"翻新"公司的老項(xiàng)目的時(shí)候挣柬,發(fā)現(xiàn)一個(gè)奇怪的問(wèn)題:
在一個(gè) block 中钥顽,我使用了 RAC 為了避免 block 循環(huán)引用而定義的兩個(gè)宏:
@weakify
和@strongify
庇忌,但是如果在 block 內(nèi)部使用下劃線屬性(成員變量)舞箍,還是會(huì)導(dǎo)致循環(huán)引用。
很多人都知道怎么處理這個(gè)問(wèn)題皆疹,在使用了@weakify
和@strongify
的情況下疏橄,在 block 內(nèi)部像self -> ivar
這樣使用成員變量就可以避免循環(huán)引用了,但是為什么這樣用就沒(méi)問(wèn)題呢略就?使用了@weakify
和@strongify
兩個(gè)宏之后發(fā)生了什么呢软族?帶著你在使用 block 時(shí)出現(xiàn)過(guò)的疑問(wèn),在后面的內(nèi)容中你可能會(huì)得到答案残制。
block是什么
block 是用于創(chuàng)建匿名函數(shù)的 C 語(yǔ)言擴(kuò)展立砸。用戶使用 block 指針與 block 對(duì)象進(jìn)行交互并傳輸 block 對(duì)象,block 指針表示為普通指針初茶。block 可以從局部變量中捕獲值;發(fā)生這種情況時(shí)颗祝,必須動(dòng)態(tài)分配內(nèi)存。初始分配在棧上完成恼布,但 runtime 提供了一個(gè)Block_copy函數(shù)螺戳,給定一個(gè) block 指針,將底層 block 對(duì)象復(fù)制到堆中折汞,將其引用計(jì)數(shù)設(shè)置為1并返回新的 block 指針倔幼,或者(如果 block 對(duì)象已經(jīng)在堆上)將其引用計(jì)數(shù)增加1.配對(duì)函數(shù)是Block_release,它將引用計(jì)數(shù)減少1并在計(jì)數(shù)達(dá)到零并且在堆上時(shí)銷(xiāo)毀對(duì)象爽待。翻譯自蘋(píng)果文檔
上面的翻譯來(lái)自于 谷歌翻譯~损同。我對(duì)于 block 的理解就是一個(gè)指針翩腐,指向一個(gè)帶有函數(shù)指針 (用于執(zhí)行block內(nèi)的代碼) 的結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)有許多捕獲的成員變量膏燃。在 ARC 環(huán)境下 block 會(huì)從 棧中自動(dòng)復(fù)制到堆中茂卦,方便 runtime 管理內(nèi)存生命周期;如果內(nèi)部有全局變量則復(fù)制到數(shù)據(jù)區(qū)组哩,生命周期為程序創(chuàng)建到程序結(jié)束等龙。
[站外圖片上傳中...(image-1d1a52-1561035272667)]
block的數(shù)據(jù)結(jié)構(gòu)
block 的數(shù)據(jù)結(jié)構(gòu)定義如下
[站外圖片上傳中...(image-f5b34b-1561035272667)]
結(jié)構(gòu)體定義:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;// sizeof(struct Block_layout)
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
通過(guò)它的數(shù)據(jù)結(jié)構(gòu),我們知道一個(gè) block 實(shí)際上是由5部分組成的
- isa 指針伶贰,所有對(duì)象都有該指針蛛砰,用于實(shí)現(xiàn)對(duì)象相關(guān)的功能
- flags,用于按 bit 位表示一些 block 的附加信息
- reserved黍衙,保留變量
- invoke暴备,函數(shù)指針,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址
- descriptor们豌, 表示該 block 的附加描述信息,主要是 size 大小浅妆,以及 copy 和 dispose 函數(shù)的指針
block的幾種的類(lèi)型
常見(jiàn)的 block 有下面三種望迎,不用類(lèi)型的 block 存放不同的區(qū)域,在 ARC 環(huán)境下只有
_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
兩種類(lèi)型的 block
- _NSConcreteGlobalBlock 全局的靜態(tài) block凌外,不會(huì)訪問(wèn)任何外部變量辩尊。
- _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀康辑。
- _NSConcreteMallocBlock 保存在堆中的 block摄欲,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷(xiāo)毀。
下面是詳細(xì)的介紹
_NSConcreteStackBlock
該類(lèi)型的 block 僅存在在 MRC 環(huán)境中疮薇,數(shù)據(jù)存放在棧區(qū)胸墙,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀。在 ARC 環(huán)境中按咒,不存在_NSConcreteStackBlock
類(lèi)型迟隅,只存在_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
兩個(gè)類(lèi)型。在下面的例子中励七, block 的類(lèi)型的打印結(jié)果是__NSMallocBlock__
智袭。原因可能是因?yàn)閏語(yǔ)言的結(jié)構(gòu)體中,編譯器不能很好地管理初始化和銷(xiāo)毀掠抬,這樣對(duì)內(nèi)存管理來(lái)說(shuō)很不方便吼野,所以就將 block 放到堆上,使用 runtime 來(lái)管理它們的生命周期两波。
int val = 1;
void(^textBlock)(void) = ^{
NSLog(@"[block] val<%p>: %d", &val, val);
NSLog(@"val: %d", val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"textBlock: %@", textBlock);
打印結(jié)果為:
val<0x16b523d1c>: 1
[block] val<0x280a9fcb0>: 1
textBlock: <__NSMallocBlock__: 0x28076a4c0>
下面使用 clang -rewrite-objc filename
將代碼轉(zhuǎn)換成 C++ 的實(shí)現(xiàn), 下面是關(guān)鍵部分的代碼
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
int val;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
static int static_v = 1;
int val = 1;
void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, val));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}
- 其中
__MyObject__test_block_impl_0
是 block 的結(jié)構(gòu)體類(lèi)型 -
__MyObject__test_block_func_0
是 block 實(shí)現(xiàn)的函數(shù)瞳步,在__MyObject__test_block_impl_0
內(nèi)有一個(gè)指針FuncPtr
指向該函數(shù) -
__MyObject__test_block_desc_0
是 block 附件描述信息的結(jié)構(gòu)體闷哆,包含著 block 結(jié)構(gòu)體大小, copy 和 dispose 函數(shù)指針(這兩個(gè)函數(shù)后面后講到)等的描述信息谚攒,在__MyObject__test_block_impl_0
內(nèi)有一個(gè)指針Desc
指向該結(jié)構(gòu)體 - 在
_I_MyObject_test
函數(shù)內(nèi)可以看到 block 的初始化阳准,void(*textBlock)(void)
說(shuō)明 textBlock 是一個(gè)指向該 block 結(jié)構(gòu)體的指針
首先觀察這個(gè)__MyObject__test_block_impl_0
的結(jié)構(gòu)體:
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
int val;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 使用 clang 轉(zhuǎn)換過(guò)的實(shí)現(xiàn)是 MRC 環(huán)境的,所以 isa 指針指向 _NSConcreteStackBlock 類(lèi)型
- 在這個(gè)結(jié)構(gòu)體中可以看到一個(gè)成員變量
int val;
馏臭,沒(méi)錯(cuò)野蝇,它就是 block 捕獲的局部變量,從構(gòu)造函數(shù)__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val)
中可以看到括儒,block 僅僅捕獲了該變量的值 -
__MyObject__test_block_impl_0
中由于增加了一個(gè)變量 val绕沈,所以結(jié)構(gòu)體的大小變大了,結(jié)構(gòu)體大小被寫(xiě)在了__MyObject__test_block_desc_0
中 - block 捕獲外部變量?jī)H僅只 block 閉包里面會(huì)用到的值帮寻,其他用不到的值乍狐,它并不會(huì)去捕獲。
再看一下__MyObject__test_block_func_0
這個(gè)函數(shù)的實(shí)現(xiàn):
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
}
我們可以發(fā)現(xiàn)固逗,系統(tǒng)自動(dòng)給我們加上的注釋?zhuān)琤ound by copy浅蚪,自動(dòng)變量 val 雖然被捕獲進(jìn)來(lái)了,但是是用 __cself->val 來(lái)訪問(wèn)的烫罩。block 僅僅捕獲了 val 的值惜傲,并沒(méi)有捕獲 val 的內(nèi)存地址。所以在__MyObject__test_block_func_0 這個(gè)函數(shù)中即使我們重寫(xiě)這個(gè)自動(dòng)變量 val 的值贝攒,依舊沒(méi)法去改變block外面變量 val 的值盗誊。
小結(jié)一下:
基本數(shù)據(jù)類(lèi)型的變量是以值傳遞方式傳遞到 block 的構(gòu)造函數(shù)里面去的。block 只捕獲 block 中會(huì)用到的變量隘弊。由于只捕獲了自動(dòng)變量的值哈踱,并非內(nèi)存地址,所以 block 內(nèi)部不能改變變量的值梨熙。
_NSConcreteMallocBlock
修改一下上面的代碼:
__block int val = 1;
void(^textBlock)(void) = ^{
val++;
NSLog(@"[block] val<%p>: %d", &val, val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"val<%p>: %d", &val, val);
NSLog(@"textBlock: %@", textBlock);
打印輸出為:
val<0x282db0858>: 1
[block] val<0x282db0858>: 2
val<0x282db0858>: 2
textBlock: <__NSMallocBlock__: 0x2823d3450>
重新用 clang 生成的c++實(shí)現(xiàn)
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val)++;
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}
在重新生成的代碼中开镣,我們看到新增了一個(gè)名為__Block_byref_val_0
的結(jié)構(gòu)體,它是用來(lái)替代我們__block
修飾的變量 val 的咽扇。
- 它的第一個(gè)指針是 isa哑子,說(shuō)明它也是一個(gè)對(duì)象。
- 第二個(gè)指針是指向自身類(lèi)的指針
__forwarding
- 第三個(gè)是一個(gè)標(biāo)記 flag
- 第四個(gè)是結(jié)構(gòu)體的大小
- 第五個(gè)是變量 val 的值
在函數(shù)static void _I_MyObject_test(MyObject * self, SEL _cmd)
我們可以看到該結(jié)構(gòu)體的初始化代碼:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
- 在初始化時(shí)肌割, isa 指向了一個(gè)空指針
-
__forwarding
指向了自己的地址 - 1是變量 val 的值卧蜓。
使用 __block修飾的變量,無(wú)論是基本數(shù)據(jù)類(lèi)型還是 OC 的類(lèi)把敞,在編譯之后都是轉(zhuǎn)換成一個(gè)新的結(jié)構(gòu)體弥奸,該結(jié)構(gòu)體的
__forwarding
指針會(huì)指向自己的地址,而成員變量 val 則為編譯前的類(lèi)型和值奋早。至于這樣的目的是什么盛霎,可以接著看下面赠橙。
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};
在__MyObject__test_block_desc_0
這個(gè)結(jié)構(gòu)體中,我們發(fā)現(xiàn)比之前的代碼多了一個(gè) copy
和 dispose
的函數(shù)指針愤炸。在c語(yǔ)言的結(jié)構(gòu)體中期揪,編譯器沒(méi)有很好地進(jìn)行初始化和銷(xiāo)毀,這樣對(duì)內(nèi)存管理來(lái)說(shuō)很不方便规个,所以就在增加了這兩個(gè)函數(shù)指針凤薛,方便進(jìn)行內(nèi)存管理。copy函數(shù)把block從棧上拷貝到堆上诞仓,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷(xiāo)毀掉缤苫。
-
copy
和dispose
這兩個(gè)函數(shù)指針對(duì)應(yīng)的兩個(gè)函數(shù)實(shí)現(xiàn)
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
-
__MyObject__test_block_copy_0
函數(shù)實(shí)現(xiàn)中出現(xiàn)了方法_Block_object_assign
, -
__MyObject__test_block_dispose_0
函數(shù)實(shí)現(xiàn)中出現(xiàn)了方法_Block_object_dispose
。
下面是這兩個(gè)方法的申明:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
下面是這兩個(gè)方法的實(shí)現(xiàn):
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
void _Block_release(void *arg) {
// 1
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3
if (newCount > 0) return;
// 4
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}
}
-
_Block_copy_internal
是Block_copy
的一個(gè)實(shí)現(xiàn)墅拭,實(shí)現(xiàn)了從_NSConcreteStackBlock復(fù)制到_NSConcreteMallocBlock的過(guò)程活玲,有9個(gè)步驟。- 在第8步中我們可以看到 isa 指針指向了
_NSConcreteMallocBlock
- 在第8步中我們可以看到 isa 指針指向了
-
_Block_release
是Block_release
的一個(gè)實(shí)現(xiàn)谍婉,實(shí)現(xiàn)了一個(gè)block釋放的過(guò)程舒憾,有6個(gè)步驟
扯的有點(diǎn)遠(yuǎn)了,現(xiàn)在讓我們總結(jié)一下 __block
修飾的變量在block內(nèi)發(fā)生了什么穗熬。
- block 會(huì)在棧中被創(chuàng)建镀迂,然后通過(guò)
Block_copy
函數(shù)復(fù)制到堆中。由 runtime 管理它的生命周期 - 使用 __block 修飾的變量死陆,在編譯后會(huì)變成一個(gè)新的對(duì)象。在初始化時(shí)唧瘾,成員變量
__forwarding
會(huì)指向棧中該變量的地址措译,val 為該變量原本的值。當(dāng) block 的成員變量__Block_byref_val_0
從棧中復(fù)制到堆中時(shí)饰序,成員變量__Block_byref_val_0
的地址可能改變了领虹,但是__forwarding
指針指向的結(jié)構(gòu)體是不會(huì)變的,仍然在棧中求豫。 - block 的實(shí)現(xiàn)函數(shù)
__MyObject__test_block_func_0
塌衰,block 通過(guò)__Block_byref_val_0 *val = __cself->val;(val->__forwarding->val)++
變量的地址修改 val,所以在 block 內(nèi)部修改變量 val 是會(huì)影響到 block 外部的變量蝠嘉。 - 這就是為什么 block 內(nèi)部和外部 val 的地址不同的原因(一個(gè)在棧上最疆,一個(gè)在堆上)。因?yàn)樗麄?code>__forwarding指向的結(jié)構(gòu)體是一樣的蚤告,所以在 block 內(nèi)部修改變量會(huì)影響到外部努酸,
_NSConcreteGlobalBlock
block 內(nèi)部只用到全局變量,包括全局變量
杜恰,靜態(tài)全局變量
获诈,靜態(tài)變量
仍源,以及上述 block 的 copy 版本。數(shù)據(jù)存放在數(shù)據(jù)區(qū)舔涎,生命周期從應(yīng)用創(chuàng)建到應(yīng)用結(jié)束笼踩。
int global_v = 1;
static int static_global_v = 1;
@implementation MyObject
- (void)test
{
static int static_v = 1;
NSLog(@"val<%p>: %d", &static_v, static_v);
NSLog(@"global_v<%p>: %d", &global_v, global_v);
NSLog(@"static_global_v<%p>: %d", &static_global_v, static_global_v);
void(^textBlock)(void) = ^{
static_v++;
global_v++;
static_global_v++;
NSLog(@"[block] val<%p>: %d", &static_v, static_v);
NSLog(@"[block] global_v<%p>: %d", &global_v, global_v);
NSLog(@"[block] static_global_v<%p>: %d", &static_global_v, static_global_v);
};
textBlock();
NSLog(@"textBlock: %@", textBlock);
}
打印信息為:
val<0x1034b8114>: 1
global_v<0x1034b8110>: 1
static_global_v<0x1034b8118>: 1
[block] val<0x1034b8114>: 2
[block] global_v<0x1034b8110>: 2
[block] static_global_v<0x1034b8118>: 2
textBlock: <__NSGlobalBlock__: 0x10343da40>
clang 之后 C++ 實(shí)現(xiàn):
int global_v = 1;
static int static_global_v = 1;
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
int *static_v;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int *_static_v, int flags=0) : static_v(_static_v) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int *static_v = __cself->static_v; // bound by copy
(*static_v)++;
global_v++;
static_global_v++;
}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
static int static_v = 1;
}
- block 僅僅捕獲了靜態(tài)變量 static_v 的地址作為自己的成員變量,因此在內(nèi)部修改該變量可以影響到 block 外部亡嫌。block 內(nèi)部和外部該變量的地址相等
- 全局變量 global_v 和全局靜態(tài)變量 static_global_v 并沒(méi)有被 block 捕獲嚎于,因?yàn)樗麄円呀?jīng)被保存在數(shù)據(jù)區(qū)中,可以直接使用
由于 clang 改寫(xiě)的方式跟 LLVM 不太一樣昼伴,在這里并沒(méi)有開(kāi)啟ARC匾旭,所以這里我們看到 isa 指向的還是 _NSConcreteStackBlock,但在開(kāi)啟ARC的時(shí)候圃郊,block 應(yīng)該是 _NSConcreteGlobalBlock 類(lèi)型价涝。
block 與 self
在前面的部分,我們已經(jīng)分析過(guò) 局部變量持舆,靜態(tài)變量色瘩,全局變量,全局靜態(tài)變量在 block 時(shí)的情況逸寓,那么居兆,還有一種特殊的變量 self,它在 block 內(nèi)部時(shí)又是怎么樣運(yùn)行的呢竹伸?
@interface MyObject () {
NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
- (void)test
{
self.name = @"n";
_age = @"10";
NSLog(@"self: %@", self);
void(^textBlock)(void) = ^{
self.name = @"a";
_age = @"11";
NSLog(@"[block] self: %@", self);
NSLog(@"[block] name<%p>: %@", self.name, self.name);
NSLog(@"[block] age<%p>: %@", _age, _age);
};
NSLog(@"name<%p>: %@", self.name, self.name);
NSLog(@"age<%p>: %@", _age, _age);
textBlock();
NSLog(@"name<%p>: %@", self.name, self.name);
NSLog(@"age<%p>: %@", _age, _age);
}
打印結(jié)果:
name<0x102804818>: n
age<0x102804838>: 10
[block] name<0x102804858>: a
[block] age<0x102804878>: 11
name<0x102804858>: a
age<0x102804878>: 11
clang之后的C++實(shí)現(xiàn)
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_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__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *self = __cself->self; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_0);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_1;
void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}
- 在
__MyObject__test_block_impl_0
中我們可以看到self
也被 block 捕獲成了成員變量 - 在
__MyObject__test_block_impl_0
的構(gòu)造函數(shù)中我們可以看到 self 被當(dāng)做參數(shù)被傳入泥栖,而不是 self 的地址 - 因?yàn)?block 在內(nèi)部和外部 self 指向的是相同的 MyObject 結(jié)構(gòu)體,所以在 block 內(nèi)部對(duì) self 成員變量進(jìn)行修改會(huì)影響到 block 外部
- block 的結(jié)構(gòu)體會(huì)強(qiáng)引用 self勋篓,所以需要小心使用吧享,否則會(huì)引起循環(huán)應(yīng)用
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *self = __cself->self; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;
}
block 內(nèi)部使用屬性和成員變量是不一樣的。直接使用屬性時(shí)譬嚣,走的是 obj_msgSend 消息發(fā)送(具體可以研究這篇博客)钢颂,而在使用成員變量時(shí),應(yīng)該是先通過(guò) self 得到結(jié)構(gòu)體的首地址拜银,然后通過(guò)成員變量的偏移量然直接使用這個(gè)成員變量(其實(shí)我也沒(méi)很理解殊鞭。。尼桶。)
小結(jié)一下:
- block 內(nèi)部使用 self 時(shí)的情況跟使用局部變量的情況是比較類(lèi)似的操灿,block 會(huì)捕獲 self 的值而不是地址當(dāng)做成員變量
- 在 block 內(nèi)部使用屬性和成員變量的情況是不一樣的
__weak與__strong
我們都知道使用__weak和__strong修飾符可以避免在block的使用中出現(xiàn)循環(huán)引用的問(wèn)題,這是為什么呢泵督?先讓我們了解一下這兩個(gè)修飾符吧牲尺!
ARC 環(huán)境下,OC的對(duì)象面前都需要加上所有權(quán)的修飾符,所有的修飾符有以下4種
- __strong修飾符
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
默認(rèn)的修飾符是__strong谤碳。
ARC下溃卡,self既不是strong也不是weak,而是unsafe_unretained的蜒简,也就是說(shuō)瘸羡,入?yún)⒌膕elf被表示為:(init系列方法的self除外)來(lái)源:博客
- (void)start {
const __unsafe_unretained MyObject *self;
}
想要弄清__weak與__strong的實(shí)現(xiàn)原理,需要研究一下clang中關(guān)于ARC的文檔搓茬,有興趣可以點(diǎn)進(jìn)去仔細(xì)看看犹赖。
__strong
id __strong object = [[NSObject alloc] init];
在終端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m
轉(zhuǎn)換成 C++ 的實(shí)現(xiàn)
id __attribute__((objc_ownership(strong))) object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
= 右邊的代碼意思應(yīng)該是對(duì) NSObject 這個(gè)類(lèi)發(fā)送 alloc 消息,然后再對(duì)生成的對(duì)象發(fā)送 init 消息卷仑,這兩個(gè)方法的實(shí)現(xiàn)可以在 runtime 中找到峻村,代碼我也貼到下面了
= 左邊的代碼,我不大理解objc_ownership
這個(gè)函數(shù)锡凝,查了下搜不到是啥意思粘昨,看字面意思應(yīng)該是兩個(gè)對(duì)象間的持有關(guān)系,也就是自己持有自己的意思窜锯。
+ alloc
{
return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}
- init
{
return self;
}
__weak
id __strong object = [[NSObject alloc] init];
id __weak weakSelf = object;
在終端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m
轉(zhuǎn)換成 C++ 實(shí)現(xiàn)
id __attribute__((objc_ownership(strong))) object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) weakSelf = object;
相應(yīng)的會(huì)調(diào)用
objc_initWeak(&weakSelf,object);
objc_destoryWeak(&weakSelf);
objc_initWeak
方法的文檔說(shuō)明
Precondition: object is a valid pointer which has not been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is zero-initialized. Otherwise, object is registered as a __weak object pointing to value. Equivalent to the following code:id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}
這個(gè)函數(shù)會(huì)把傳入的 object 置為nil张肾,然后執(zhí)行objc_storeWeak
函數(shù)。
那么objc_storeWeak
函數(shù)是干什么的呢锚扎?下面是這個(gè)方法的說(shuō)明
Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value.
Returns the value of object after the call.
objc_storeWeak函數(shù)的用途就很明顯了吞瞪。由于weak表也是用Hash table實(shí)現(xiàn)的,所以objc_storeWeak函數(shù)就把第一個(gè)入?yún)⒌淖兞康刂纷?cè)到weak表中驾孔,然后根據(jù)第二個(gè)入?yún)?lái)決定是否移除芍秆。如果第二個(gè)參數(shù)為0,那么就把__weak變量從weak表中刪除記錄翠勉,并從引用計(jì)數(shù)表中刪除對(duì)應(yīng)的鍵值記錄
所以如果__weak引用的原對(duì)象如果被釋放了妖啥,那么對(duì)應(yīng)的__weak對(duì)象就會(huì)被指為nil。原來(lái)就是通過(guò)objc_storeWeak函數(shù)這些函數(shù)來(lái)實(shí)現(xiàn)的眉菱。
接下來(lái)是 objc_destoryWeak
函數(shù)的實(shí)現(xiàn)
void objc_destroyWeak(id *object) {
objc_storeWeak(object, nil);
}
還是調(diào)用上面的objc_storeWeak
函數(shù)迹栓,因?yàn)閭魅氲膙alue為nil掉分,所以object將從weak表中刪除并且置為nil
__weak與__strong的作用
終于講到這兩個(gè)所有權(quán)修飾符的作用了俭缓。
首先是不使用這兩個(gè)修飾符時(shí)的情況。在上面我們已經(jīng)講到過(guò) block 存在 self 的一種情況了酥郭,下面我們要講一下 block 存在 self 并且 self 強(qiáng)應(yīng)用 block 時(shí)的情況
@interface MyObject ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end
@implementation MyObject
- (void)test
{
self.textBlock = ^{
self.name = @"n";
}
}
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.object = [[MyObject alloc] init];
[self.object test];
}
對(duì)于 MyObject 來(lái)說(shuō)是造成了循環(huán)引用的华坦,因?yàn)樗鼜?qiáng)引用了 block,而 block 內(nèi)部也強(qiáng)引用著 self不从,所以 MyObject 是不能被dealloc的惜姐,但奇怪的是,將 MyObject 當(dāng)做屬性的 OneViewController 竟然可以dealloc,這估計(jì)是另一個(gè)問(wèn)題了歹袁,等我有空再去研究一下這個(gè)坷衍。。条舔。
使用 clang 得到的C++實(shí)現(xiàn)枫耳,這邊只截取了block結(jié)構(gòu)體和初始化block部分
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 初始化
((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344)
在這個(gè)部分中可以看到 block 將 self(MyObject *指針)捕獲成了自己的成員變量了(強(qiáng)引用), 而self指針的成員變量又包含block,造成循環(huán)引用孟抗。
僅僅使用__weak
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end
@implementation MyObject
- (void)test
{
__weak typeof(self) weakSelf = self;
self.textBlock = ^{
weakSelf.name = @"n";
NSLog(@"hh");
};
self.textBlock();
}
使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m
得到C++實(shí)現(xiàn)
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak weakSelf;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)weakSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_970d18_mi_0);
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}
蘋(píng)果使用一個(gè)全局的 weak 表來(lái)保存所有的 weak 引用迁杨。并將對(duì)象作為鍵,weak_entry_t 作為值凄硼。weak_entry_t 中保存了所有指向該對(duì)象的 weak 指針铅协。當(dāng)被指向的對(duì)象執(zhí)行 dealloc 時(shí)候,將所有指向該對(duì)象的 weak 指針的設(shè)置為nil摊沉。
- block 將 __weak 修飾的 self 捕獲為成員變量
- 當(dāng) self 執(zhí)行dealloc時(shí)狐史,block 內(nèi)的 self 置為nil,從而打破循環(huán)引用
- 當(dāng) self delloac 之后坯钦,在調(diào)用 block 的函數(shù)指針预皇,block 內(nèi)部的self置為nil。
同時(shí)使用__weak與__strong
@interface MyObject ()
//{
// NSString *_age;
//}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end
@implementation MyObject
- (void)test
{
__weak typeof(self) weakSelf = self;
self.textBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"n";
NSLog(@"hh");
};
self.textBlock();
}
使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m
得到C++實(shí)現(xiàn)
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak weakSelf;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy
__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)strongSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_0010b9_mi_0);
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}
-
__MyObject__test_block_impl_0
block 仍然是將 __weak 修飾的 self 捕獲為成員變量 - 當(dāng) self 執(zhí)行dealloc時(shí)婉刀,block 內(nèi)的 self 會(huì)被置為nil吟温,從而打破循環(huán)引用
- block 內(nèi)的代碼在
__MyObject__test_block_func_0
函數(shù)內(nèi),當(dāng)使用strongSelf
時(shí)突颊,會(huì)先取出__weak修飾的成員變量self:MyObject *const __weak weakSelf = __cself->weakSelf;
, 然后再生成一個(gè)__strong修飾的局部變量__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;
鲁豪,self 的引用計(jì)數(shù) +1。這樣的目的是在 block 內(nèi)的代碼塊執(zhí)行完之前避免 self 被dealloc掉律秃。當(dāng) block 執(zhí)行完畢之后爬橡,局部變量 strongSelf 被釋放,self 的引用計(jì)數(shù) -1棒动。
@weakify 和 @strongify
這兩個(gè)是RAC中避免Block循環(huán)引用而開(kāi)發(fā)的2個(gè)宏糙申,實(shí)現(xiàn)過(guò)程很牛,值得我們學(xué)習(xí)船惨。限于篇幅柜裸,我就不分析了,想了解可以點(diǎn)開(kāi)這篇博客粱锐。
這兩個(gè)宏展開(kāi)下來(lái)就相當(dāng)于:
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
回到開(kāi)頭
好了疙挺,不知道你看了這么多頭暈了沒(méi)有。怜浅。铐然。下面讓我們回到開(kāi)頭我碰到的那個(gè)問(wèn)題蔬崩,為什么我使用了 @weakify 和 @strongify,然后直接使用下劃線的成員變量還是會(huì)造成循環(huán)引用搀暑。原因就是_ivar
直接使用成員變量沥阳,self 跟 weakSelf會(huì)同時(shí)被 block 捕獲成 block 的成員變量,注意:self 還是會(huì)被 block 捕獲的(前面好像沒(méi)寫(xiě)例子自点,不過(guò)你可以自己寫(xiě)寫(xiě)看)沪袭,導(dǎo)致 block 還是強(qiáng)引用著 self,導(dǎo)致循環(huán)引用樟氢。解決辦法就是 strongSelf -> ivar
這樣使用成員變量
總結(jié)
-
block 會(huì)捕捉 block 內(nèi)部的變量
- 當(dāng)變量類(lèi)型是局部變量(基本數(shù)據(jù)類(lèi)型時(shí)或 oc 類(lèi))冈绊,僅捕獲該變量的值,所以 block 內(nèi)部和外部這兩個(gè)變量的地址是不一樣的埠啃,在block 內(nèi)部修改變量的值也不會(huì)影響 block 外部的變量
- 當(dāng)變量是 self 時(shí)的情況跟 局部變量時(shí)是差不多的
- 當(dāng)變量類(lèi)型是__block修飾的布局變量(基本數(shù)據(jù)類(lèi)型或者 oc 類(lèi))死宣,會(huì)新構(gòu)建一個(gè)結(jié)構(gòu)體,其中成員變量
__forwarding
會(huì)指向棧中該變量的地址碴开,因此在 block 內(nèi)部修改該變量會(huì)影響 block 外部的變量 - 當(dāng)變量是全局變量或者全局靜態(tài)變量時(shí)毅该,block 不會(huì)捕獲該變量,因?yàn)樽兞恳呀?jīng)存在在數(shù)據(jù)區(qū)潦牛,可以直接調(diào)用眶掌。此時(shí) block 也保存在數(shù)據(jù)區(qū)
- 當(dāng)變量是靜態(tài)變量時(shí),block 會(huì)捕獲該變量的地址巴碗,因此在 block 內(nèi)部修改該變量會(huì)影響 block 外部的變量
block 結(jié)構(gòu)體中的成員變量
descriptor
包含著copy
和dispose
兩個(gè)函數(shù)指針朴爬。copy 函數(shù)把 block 從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷(xiāo)毀掉橡淆。蘋(píng)果使用一個(gè)全局的 weak 表來(lái)保存所有的 weak 引用召噩。并將對(duì)象作為鍵,weak_entry_t 作為值逸爵。weak_entry_t 中保存了所有指向該對(duì)象的 weak 指針找筝。當(dāng)被指向的對(duì)象執(zhí)行 dealloc 時(shí)候赛糟,將所有指向該對(duì)象的 weak 指針的設(shè)置為nil。
在 block 外部使用 __weak 的原因是事扭,讓 block 將這個(gè) __weak修飾的變量捕獲成自己的成員變量张足,這樣當(dāng)外面的變量被 dealloc 后显拜,block 內(nèi)的該成員變量也將置為 nil枢泰,避免循環(huán)引用
在 block 里面使用的 __strong 修飾的 weakSelf 是為了在函數(shù)生命周期中防止 self 提前釋放笨蚁。strongSelf是一個(gè)局部變量,當(dāng)block內(nèi)的代碼執(zhí)行完畢就會(huì)釋放致稀,不會(huì)對(duì) self 進(jìn)行一直進(jìn)行強(qiáng)引用冈闭。
引用
ARC對(duì)self的內(nèi)存管理
深入研究 Block 捕獲外部變量和 __block 實(shí)現(xiàn)原理
深入研究 Block 用 weakSelf俱尼、strongSelf抖单、@weakify、@strongify 解決循環(huán)引用
談Objective-C block的實(shí)現(xiàn)