蘋(píng)果官方資源opensource
objc4-838可編譯聯(lián)調(diào)源碼
libclosure源碼
本章節(jié)探究:
1.Block的類(lèi)型
2.Block的實(shí)質(zhì)
3.內(nèi)容捕獲
4.__block的底層原理
5.Block的循環(huán)引用
6.面試題
一、Block的類(lèi)型
1.Global Block - 全局

2.Malloc Block - 堆

3.Stack Block - 棧

__weak修飾block會(huì)在出方法棧的時(shí)候立馬被銷(xiāo)毀。
Block的類(lèi)型總結(jié):
全局Block:沒(méi)有捕獲外部的局部變量;只使用了全局變量/靜態(tài)變量。
堆Block:捕獲了局部變量;賦值給了強(qiáng)引用。
棧Block:捕獲了局部變量;賦值給了弱引用。
為什么
Block要?copy關(guān)鍵字修飾?
Block在創(chuàng)建的時(shí)候,它的內(nèi)存是分配在方法棧上的,?不是在堆上。
棧區(qū)的特點(diǎn)是:對(duì)象隨時(shí)有可能被銷(xiāo)毀,?旦被銷(xiāo)毀,在調(diào)?的時(shí)候,就會(huì)造成系統(tǒng)的崩潰。所以我們要使?copy把它拷?到堆上。
在ARC下,對(duì)于Block使?copy與strong其實(shí)都?樣,因?yàn)?code>block的retain就是?copy來(lái)實(shí)現(xiàn)的。所以在ARC下block使?copy和strong都可以。
二、Block的本質(zhì)

把上面代碼編譯成.cpp文件
$ clang -rewrite-objc main.m
打開(kāi)main.cpp,找到main函數(shù)

被編譯之后Block就變成了__main_block_impl_0的數(shù)據(jù)結(jié)構(gòu)了

1.NSObject *objc就是捕獲的外部局部變量;
2.impl.isa = &_NSConcreteStackBlock;這行代碼可以看出Block是一個(gè)對(duì)象;
3.Block在創(chuàng)建的時(shí)候它是棧Block。
棧Block具備捕獲外部局部變量的能力。
Block不是在調(diào)用的時(shí)候才捕獲外部局部變量,而是在聲明的時(shí)候就已經(jīng)捕獲了。
1.匯編分析Block的底層調(diào)用邏輯

- 打開(kāi)匯編模式
Always Show Disassembly

給objc_retainBlock打上符號(hào)斷點(diǎn),看看它底層怎么調(diào)用的

_Block_copy的實(shí)現(xiàn)是放在libclosure源碼里的,參數(shù)是block
打開(kāi)objc4源碼找到objc_retainBlock

2.源碼分析Block的底層調(diào)用邏輯
打開(kāi)libclosure源碼找到_Block_copy
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// 拷貝 block,
// 如果原來(lái)就在堆上,就將引用計(jì)數(shù)加 1;
// 如果原來(lái)在棧上,會(huì)拷貝到堆上,引用計(jì)數(shù)初始化為 1,并且會(huì)調(diào)用 copy helper 方法(如果存在的話);
// 如果 block 在全局區(qū),不用加引用計(jì)數(shù),也不用拷貝,直接返回 block 本身
// 參數(shù) arg 就是 Block_layout 對(duì)象,
// 返回值是拷貝后的 block 的地址
// 運(yùn)行?stack -》malloc
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 為 NULL,直接返回 NULL
if (!arg) return NULL;
// The following would be better done as a switch statement
// 強(qiáng)轉(zhuǎn)為 Block_layout 類(lèi)型
aBlock = (struct Block_layout *)arg;
const char *signature = _Block_descriptor_3(aBlock)->signature;
// 1.如果現(xiàn)在已經(jīng)在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只將引用計(jì)數(shù)加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 2.如果 block 在全局區(qū),不用加引用計(jì)數(shù),也不用拷貝,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
// 3.block 現(xiàn)在在棧上,現(xiàn)在需要將其拷貝到堆上
// 在堆上重新開(kāi)辟一塊和 aBlock 相同大小的內(nèi)存
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 開(kāi)辟失敗,返回 NULL
if (!result) return NULL;
// 將 aBlock 內(nèi)存上的數(shù)據(jù)全部復(fù)制新開(kāi)辟的 result 上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 將 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清為 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 將 result 標(biāo)記位在堆上,需要手動(dòng)釋放;并且引用計(jì)數(shù)初始化為 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// copy 方法中會(huì)調(diào)用做拷貝成員變量的工作
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
參數(shù)arg是block,它會(huì)先把block強(qiáng)轉(zhuǎn)成Block_layout這個(gè)結(jié)構(gòu)體類(lèi)型。
Block的本質(zhì)就是Block_layout結(jié)構(gòu)體。(后面會(huì)分析)
- 1.
aBlock已經(jīng)在堆上,只將引用計(jì)數(shù)+1; - 2.
aBlock在全局區(qū),不做操作返回出去; -
3.
aBlock在棧上,現(xiàn)在需要將其拷貝到堆上,在堆上重新開(kāi)辟一塊和aBlock相同大小的內(nèi)存。(原本的棧上的在出方法棧的時(shí)候就釋放掉了)
Block_layout結(jié)構(gòu)體

(注意:Block_layout擁有Block_descriptor_2里的copy和dispose函數(shù)是在捕獲了堆區(qū)外部局部變量的時(shí)候才會(huì)有。用于這個(gè)變量的引用計(jì)數(shù)管理)
-
_Block_call_copy_helper函數(shù)的作用是拷貝aBlock的成員變量的工作。

3.為什么Block需要調(diào)用的時(shí)候才會(huì)執(zhí)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
int a = 10;
void (^block)(void) = ^ {
NSLog(@"%d",a);
};
block();
return NSApplicationMain(argc, argv);
}
將main.m編譯成main.cpp:
$ $ clang -rewrite-objc main.m
打開(kāi)main.cpp找到mian函數(shù)

來(lái)看看__main_block_impl_0結(jié)構(gòu)體構(gòu)造方法和__main_block_func_0函數(shù)實(shí)現(xiàn)部分:

這里傳遞的a是指針(不是地址)。
給struct __block_impl impl;指定了isa、flags、FuncPtr。


__main_block_impl_0結(jié)構(gòu)體與源碼里的Block_layout是一樣的。這就證實(shí)了源碼分析部分沒(méi)有錯(cuò)。
block聲明就是把方法實(shí)現(xiàn)保存到了__block_impl這個(gè)結(jié)構(gòu)體中去;等到調(diào)用的時(shí)候從這里取出函數(shù)實(shí)現(xiàn)地址直接調(diào)用。
三、內(nèi)容捕獲
- 捕獲棧區(qū)的外部局部變量
-(void)test1 {
int a = 10;
NSLog(@"a--%p",&a);
void (^block)(void) = ^ {
NSLog(@"a--%p",&a);
};
block();
}
/**
打印結(jié)果:
a--0x7ff7bf79c29c
a--0x60000137c2f0
*/
很明顯兩個(gè)變量a同一個(gè)東西,block外部的a是棧區(qū)地址,block外部的a是堆區(qū)地址。
因?yàn)樵?code>block在棧區(qū)的時(shí)候就已經(jīng)捕獲了a,當(dāng)block被copy到堆區(qū)時(shí),原本捕獲的成員變量也需要在堆區(qū)開(kāi)辟內(nèi)存。
如果需要改變?cè)?code>a的值需要使用__block來(lái)修飾a:
-(void)test1 {
__block int a = 10;
NSLog(@"a--%p",&a);
void (^block)(void) = ^ {
NSLog(@"a--%p",&a);
a++;
};
block();
NSLog(@"a: %d", a);
}
/**
打印結(jié)果:
a--0x7ff7bf79c29c
a--0x60000137c2f0
a: 11
*/
- 捕獲堆區(qū)的外部局部變量
-(void)test2 {
NSObject *objc = [NSObject new];
NSLog(@"%@",objc);
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)));
NSLog(@"-------------------");
void (^block)(void) = ^ {
NSLog(@"%@",objc);
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
block();
}
/**
打印結(jié)果:
<NSObject: 0x600003a00310>
1
-------------------
<NSObject: 0x600003a00310>
3
*/
可以看到objc的打印是同一個(gè)東西。這是因?yàn)?code>objc所指向的內(nèi)存分配本身就在堆區(qū),block在捕獲到堆區(qū)的外部局部變量時(shí),不需要另外開(kāi)辟內(nèi)存空間,只需要堆區(qū)block的成員變量指針指向同一塊內(nèi)存空間即可。
因?yàn)闂^(qū)block和堆區(qū)block的成員變量都指向objc實(shí)際的堆區(qū)內(nèi)存區(qū)域,所以objc的引用計(jì)數(shù)+2。
- 是否能捕獲 全局變量、靜態(tài)變量
int b = 20; // 全局變量
static int c = 30; // 靜態(tài)變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
__block int a = 10; // 局部變量
NSLog(@"a--%p",&a);
NSLog(@"b--%p",&b);
NSLog(@"c--%p",&c);
void (^block)(void) = ^ {
NSLog(@"捕獲a--%p",&a);
NSLog(@"捕獲b--%p",&b);
NSLog(@"捕獲c--%p",&c);
a++; // 必須使用__block修飾才能修改局部變量a的值
};
block();
NSLog(@"a: %d", a);
return NSApplicationMain(argc, argv);
}
2022-06-13 20:33:01.485055+0800 Block的本質(zhì)[44760:11096843] a--0x7ff7b3368248
2022-06-13 20:33:01.485157+0800 Block的本質(zhì)[44760:11096843] b--0x10cb9fd58
2022-06-13 20:33:01.485189+0800 Block的本質(zhì)[44760:11096843] c--0x10cb9fd5c
2022-06-13 20:33:01.485265+0800 Block的本質(zhì)[44760:11096843] 捕獲a--0x6000004ab938
2022-06-13 20:33:01.485300+0800 Block的本質(zhì)[44760:11096843] 捕獲b--0x10cb9fd58
2022-06-13 20:33:01.485327+0800 Block的本質(zhì)[44760:11096843] 捕獲c--0x10cb9fd5c
2022-06-13 20:33:01.485347+0800 Block的本質(zhì)[44760:11096843] a: 11
可以看到捕獲前后變量a的地址明顯不一樣,那說(shuō)明他倆不是一個(gè)東西。
再把mian.m編譯成main.cpp

(請(qǐng)注意這里的a.__forwarding->a,與__block有關(guān)后面會(huì)講)
找到__main_block_impl_0結(jié)構(gòu)體聲明的地方

Block只捕獲了外部局部變量a;沒(méi)有捕獲全局b和靜態(tài)c。
Block不會(huì)捕獲全局變量和靜態(tài)變量。
Block捕獲的變量是在Block內(nèi)部生成新的指針指向捕獲變量的內(nèi)存(淺拷貝)。
四、__block修飾局部變量的底層原理
在block內(nèi)部可以修改全局變量和靜態(tài)變量的值,但是不允許修改局部變量的值。要箱子block內(nèi)部修改局部變量的值需要用__block修飾。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
__block NSObject *obc = [NSObject new];
void (^block)(void) = ^ {
NSLog(@"%@",obc);
};
block();
return NSApplicationMain(argc, argv);
}
把mian.m編譯成main.cpp,找到main函數(shù)

__main_block_impl_0是構(gòu)造函數(shù),它傳遞一個(gè)捕獲外部局部變量(__Block_byref_obc_0 *)&obc。

同時(shí)在構(gòu)造__Block_byref_obc_0的時(shí)候傳遞了__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131這兩個(gè)函數(shù)地址

這里的__forwarding是指向__Block_byref_obc_0本身。

_Block_object_assign的分析
__Block_byref_id_object_copy_131的調(diào)用時(shí)機(jī)是在Block從??截惖蕉训臅r(shí)候,也就是Block源碼分析環(huán)節(jié)里的_Block_call_copy_helper函數(shù)

__Block_byref_id_object_copy_131的底層其實(shí)就是調(diào)用了_Block_object_assign函數(shù)
在源碼中找到_Block_object_assign的聲明
//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// 當(dāng) block 和 byref 要持有對(duì)象時(shí),它們的 copy helper 函數(shù)會(huì)調(diào)用這個(gè)函數(shù)來(lái)完成 assignment,
// 參數(shù) destAddr 其實(shí)是一個(gè)二級(jí)指針,指向真正的目標(biāo)指針
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// 默認(rèn)什么都不干,但在 _Block_use_RR() 中會(huì)被 Objc runtime 或者 CoreFoundation 設(shè)置 retain 函數(shù),
// 其中,可能會(huì)與 runtime 建立聯(lián)系,操作對(duì)象的引用計(jì)數(shù)什么的
_Block_retain_object(object);
// 使 dest 指向的目標(biāo)指針指向 object
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// 使 dest 指向的拷貝到堆上object
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
// 使 dest 指向的拷貝到堆上的byref
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目標(biāo)指針指向 object
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目標(biāo)指針指向 object
*dest = object;
break;
default:
break;
}
}
如果是__block修飾的局部變量,它就會(huì)走case BLOCK_FIELD_IS_BYREF代碼段,去調(diào)用_Block_byref_copy(object);
_Block_byref_copy的源碼聲明:
// 1. 如果 byref 原來(lái)在堆上,就將其拷貝到堆上,拷貝的包括 Block_byref、Block_byref_2、Block_byref_3,
// 被 __weak 修飾的 byref 會(huì)被修改 isa 為 _NSConcreteWeakBlockVariable,
// 原來(lái) byref 的 forwarding 也會(huì)指向堆上的 byref;
// 2. 如果 byref 已經(jīng)在堆上,就只增加一個(gè)引用計(jì)數(shù)。
// 參數(shù) dest是一個(gè)二級(jí)指針,指向了目標(biāo)指針,最終,目標(biāo)指針會(huì)指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {
// arg 強(qiáng)轉(zhuǎn)為 Block_byref * 類(lèi)型
struct Block_byref *src = (struct Block_byref *)arg;
// 引用計(jì)數(shù)等于 0
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 為新的 byref 在堆中分配內(nèi)存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
// 新 byref 的 flags 中標(biāo)記了它是在堆上,且引用計(jì)數(shù)為 2。
// 為什么是 2 呢?注釋說(shuō)的是 non-GC one for caller, one for stack
// one for caller 很好理解,那 one for stack 是為什么呢?
// 看下面的代碼中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相當(dāng)于引用了 copy
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 堆上 byref 的 forwarding 指向自己
copy->forwarding = copy; // patch heap copy to point to itself
// 原來(lái)?xiàng)I系?byref 的 forwarding 現(xiàn)在也指向堆上的 byref
src->forwarding = copy; // patch stack to point to heap copy
// 拷貝 size
copy->size = src->size;
// 如果 src 有 copy/dispose helper
if (src->flags & BLOCK_BYREF_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
// 取得 src 和 copy 的 Block_byref_2
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
// copy 的 copy/dispose helper 也與 src 保持一致
// 因?yàn)槭呛瘮?shù)指針,估計(jì)也不是在棧上,所以不用擔(dān)心被銷(xiāo)毀
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
// 如果 src 有擴(kuò)展布局,也拷貝擴(kuò)展布局
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
// 沒(méi)有將 layout 字符串拷貝到堆上,是因?yàn)樗?const 常量,不在棧上
copy3->layout = src3->layout;
}
// 調(diào)用 copy helper,因?yàn)?src 和 copy 的 copy helper 是一樣的,所以用誰(shuí)的都行,調(diào)用的都是同一個(gè)函數(shù)
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
// 如果 src 沒(méi)有 copy/dispose helper
// 將 Block_byref 后面的數(shù)據(jù)都拷貝到 copy 中,一定包括 Block_byref_3
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
// src 已經(jīng)在堆上,就只將引用計(jì)數(shù)加 1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 如果
byref原來(lái)在堆上,就將其拷貝到堆上,拷貝的包括Block_byref、Block_byref_2、Block_byref_3,被__weak修飾的byref會(huì)被修改isa為_NSConcreteWeakBlockVariable,原來(lái)byref的forwarding也會(huì)指向堆上的byref; - 如果
byref已經(jīng)在堆上,就只增加一個(gè)引用計(jì)數(shù)。
理解:
我們代碼的Block在聲明的時(shí)候一定是在棧上的,當(dāng)把Block拷貝到堆上時(shí)連同捕獲的局部變量一起拷貝(因?yàn)槎即鎯?chǔ)在結(jié)構(gòu)體__block_impl),如果此時(shí)使用__block修飾局部變量了,原本棧上Block的__forwording指向棧Block自己,拷貝到堆上Block的__forwording指向堆Block自己,而此時(shí)棧上的Block的__forwording會(huì)改變指向堆上Block。
__block修飾的局部變量原本是在棧上的,需要拷貝到堆;__block修飾的局部變量原本是在堆上的,將其引用計(jì)數(shù)+1。
_Block_object_dispose的分析
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
// 當(dāng) block 和 byref 要 dispose 對(duì)象時(shí),它們的 dispose helper 會(huì)調(diào)用這個(gè)函數(shù)
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
// 如果是 byref
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
// 對(duì) byref 對(duì)象做 release 操作
_Block_byref_release(object);
break;
// 如果是 block
case BLOCK_FIELD_IS_BLOCK:
// 對(duì) block 做 release 操作
_Block_release(object);
break;
// 如果是對(duì)象
case BLOCK_FIELD_IS_OBJECT:
// 默認(rèn)啥也不干,但在 _Block_use_RR() 中可能會(huì)被 Objc runtime 或者 CoreFoundation 設(shè)置一個(gè) release 函數(shù),里面可能會(huì)涉及到 runtime 的引用計(jì)數(shù)
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
如果是__block修飾的外部局部變量,會(huì)走case BLOCK_FIELD_IS_BYREF: 調(diào)用_Block_byref_release函數(shù)(對(duì) byref 對(duì)象做 release 操作)
_Block_byref_release的源碼聲明:
// 對(duì) byref 對(duì)象做 release 操作,
// 堆上的 byref 需要 release,棧上的不需要 release,
// release 就是引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,就將 byref 對(duì)象銷(xiāo)毀
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
// 取得真正指向的 byref,如果 byref 已經(jīng)被堆拷貝,則取得是堆上的 byref,否則是棧上的,棧上的不需要 release,也沒(méi)有引用計(jì)數(shù)
byref = byref->forwarding;
// byref 被拷貝到堆上,需要 release
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
// 取得引用計(jì)數(shù)
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
// 引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,會(huì)返回 true,表示 byref 需要被銷(xiāo)毀
if (latching_decr_int_should_deallocate(&byref->flags)) {
// 如果 byref 有 dispose helper,就先調(diào)用它的 dispose helper
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
// dispose helper 藏在 Block_byref_2 里
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
對(duì) byref對(duì)象 做 release 操作,堆上的 byref 需要 release,棧上的不需要 release,release 就是引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,就將 byref 對(duì)象銷(xiāo)毀。
_Block_object_assign與_Block_object_dispose是在Block捕獲外部局部變量時(shí)成對(duì)出現(xiàn),對(duì)捕獲的外部變量進(jìn)行內(nèi)存管理(引用計(jì)數(shù)增減)。
總結(jié)
__block的底層原理:用
__block修飾的變量在編譯過(guò)后會(huì)變成__Block_byref__XXX類(lèi)型的結(jié)構(gòu)體,在結(jié)構(gòu)體內(nèi)部有一 個(gè)__forwarding的結(jié)構(gòu)體指針,指向結(jié)構(gòu)體本身。block創(chuàng)建的時(shí)候是在棧上的,在將棧block拷?到堆上的時(shí)候,同時(shí)也會(huì)將block中捕獲的對(duì)象拷?到堆上,然后就會(huì)將棧上的__block修飾對(duì)象的__forwarding指針指向堆上的拷?之后的對(duì)象。 這樣我們?cè)?code>block內(nèi)部修改的時(shí)候雖然是修改堆上的對(duì)象的值,但是因?yàn)闂I系膶?duì)象的__forwarding指針將堆和棧的對(duì)象鏈接起來(lái)。因此就可以達(dá)到修改的目的。
__block不可以用于修飾靜態(tài)變量和全局變量。
五、循環(huán)引用
- 案例一:
- (void)test {
self.block = ^{
self.name = @"AnAn";
};
}
原因:self -> block -> self。
block在聲明實(shí)現(xiàn)的時(shí)候就會(huì)捕獲外部局部變量,所以案例一不需要調(diào)用block也會(huì)產(chǎn)生循環(huán)引用。
即便把self.name改成_name,在捕獲的時(shí)候也會(huì)把self對(duì)象捕獲。
block要釋放必須self要釋放,可使用__weak去修飾self就解決循環(huán)引用:
- (void)test {
__weak typeof(self) weakSelf = self;
self.block = ^{
weakSelf.name = @"AnAn";
};
}
但是依然會(huì)有問(wèn)題,比如block里面有一個(gè)dispatch_after
-(void)test {
self.name = @"lg";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.block();
}
如果controller還沒(méi)等到執(zhí)行dispatch_after就pop/dismiss,等到3s后就會(huì)運(yùn)行dispatch_after,此時(shí)打印weakSelf.name的結(jié)果是null。
造成這樣的結(jié)果是controller在pop/dismiss后就釋放了。
解決方式一:強(qiáng)弱共舞(weak strong dance)
-(void)test {
self.name = @"lg";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%p",&strongSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
}
解決方式二:臨時(shí)變量
-(void)test1 {
self.name = @"lg";
__block MyViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
}
解決方式三:block參數(shù)
-(void)test2 {
self.name = @"lg";
self.block = ^(MyViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
}
- 案例二:
static MyViewController *_staticSelf;
-(void)test2 {
__weak typeof(self) weakSelf = self; // self是MyViewController的實(shí)例對(duì)象
_staticSelf = weakSelf;
}
// 產(chǎn)生循環(huán)引用
__weak typeof(self) weakSelf = self;的意思是用一個(gè)弱引用指針指向self所指向的堆內(nèi)存,不會(huì)對(duì)堆內(nèi)存引用計(jì)數(shù)不會(huì)+1。
而_staticSelf是不會(huì)自動(dòng)釋放的,所以self也是一直不被釋放。需要手動(dòng)把_staticSelf = nil
- 案例三:
- (void)test3 {
__weak typeof(self) weakSelf = self;
self.block1 = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%p", &strongSelf);
weakSelf.block2 = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.block2();
};
self.block1();
}
// 產(chǎn)生循環(huán)引用
weakSelf -> block2 -> StrongSelf
若把NSLog(@"%@", strongSelf); 換成 NSLog(@"%@", weakSelf);就不存在循環(huán)引用了,因?yàn)?code>block2捕獲的weakSelf是弱引用。
六、面試題
- 第1道面試題
-(void)test {
NSObject *objc = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)objc));
// 棧+1 堆+1
void(^block1)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)objc));
};
block1();
// 棧+1
void(^__weak block2)(void) = ^{
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)objc));
};
block2();
// 堆+1
void(^block3)(void) = [block2 copy];
block3();
void(^block4)(void) = [block1 copy];
block4();
}
// 13455
上面的分析已經(jīng)知道Block的本質(zhì)就是Block_layout,于是我仿照源碼寫(xiě)了一個(gè)_MyBlock結(jié)構(gòu)體:
// NSObject+Block.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(*MyBlockCopyFunction)(void *, const void *);
typedef void(*MyBlockDisposeFunction)(const void *);
typedef void(*MyBlockInvokeFunction)(void *, ...);
enum {
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};
struct _MyBlockDescriptor1 {
uintptr_t reserved;
uintptr_t size;
};
struct _MyBlockDescriptor2 {
// requires BLOCK_HAS_COPY_DISPOSE
MyBlockCopyFunction copy;
MyBlockDisposeFunction dispose;
};
struct _MyBlockDescriptor3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
// 底層
struct _MyBlock {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// 函數(shù)指針
MyBlockInvokeFunction invoke;
struct _MyBlockDescriptor1 *descriptor;
};
static struct _MyBlockDescriptor3 * _My_Block_descriptor_3(struct _MyBlock *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return nil;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct _MyBlockDescriptor1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct _MyBlockDescriptor2);
}
return (struct _MyBlockDescriptor3 *)desc;
}
static const char *MyBlockTypeEncodeString(id blockObj) {
struct _MyBlock *block = (__bridge void *)blockObj;
return _My_Block_descriptor_3(block)->signature;
}
@interface NSObject (Block)
// 打印Block 簽名
- (NSMethodSignature *)getBlcokSignature;
// 打印Block 簽名
- (NSString *)getBlcokSignatureString;
// 調(diào)用block
- (void)invokeBlock;
@end
NS_ASSUME_NONNULL_END
// NSObject+Block.m
#import "NSObject+Block.h"
@implementation NSObject (Block)
- (NSString *)getBlcokSignatureString {
NSMethodSignature *signature = self.getBlcokSignature;
if (signature) {
NSMutableString *blockSignature = [NSMutableString stringWithFormat:@"BlcokSignature: return type: %s, ", [signature methodReturnType]];
for (int i = 0; i < signature.numberOfArguments; i++) {
[blockSignature appendFormat:@"argument number: %d, argument type: %s ", i+1, [signature getArgumentTypeAtIndex:i]];
}
return blockSignature;
}
return nil;
}
- (NSMethodSignature *)getBlcokSignature {
if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:MyBlockTypeEncodeString(self)];
return signature;
}
return nil;
}
- (void)invokeBlock {
NSMethodSignature *signature = self.getBlcokSignature;
if (signature) {
// 動(dòng)態(tài)的消息轉(zhuǎn)發(fā)
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:signature];
[blockInvocation invokeWithTarget:self];
}
}
// block OC 對(duì)象
- (NSString *)description {
if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
//簽名
return [NSString stringWithFormat:@"<%@:%p>--%@", self.class, self, [self getBlcokSignatureString]];
}
return [NSString stringWithFormat:@"<%@:%p>", self.class, self];
}
@end
于是我就可以對(duì)系統(tǒng)的Block進(jìn)行強(qiáng)轉(zhuǎn)成_MyBlock類(lèi)型去操作內(nèi)存:
- 第2.1道面試題:
// ViewController.m
#import "ViewController.h"
#import "NSObject+Block.h"
@implementation ViewController
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
- (void)viewDidLoad {
[super viewDidLoad];
[self blockDemo1];
}
- (void)blockDemo1{
int a = 1;
// 棧block
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
// 強(qiáng)轉(zhuǎn)成自定義的_MyBlock結(jié)構(gòu)體類(lèi)型
struct _MyBlock *blc = (__bridge struct _MyBlock *)weakBlock;
void(^strongBlock)(void) = weakBlock;
blc->invoke = nil;
strongBlock();
}
@end
此時(shí)strongBlock和weakBlock指向的是同一塊棧block內(nèi)存;
將block的函數(shù)實(shí)現(xiàn)置為nil,會(huì)導(dǎo)致崩潰。
怎么修改不會(huì)導(dǎo)致崩潰?
void(^strongBlock)(void) = [weakBlock copy];
將棧block拷貝到堆,此時(shí)strongBlock是指向堆內(nèi)存,weakBlock是指向棧內(nèi)存。blc->invoke = nil;是修改棧block的內(nèi)容,并不影響strongBlock的調(diào)用。
- 第2.2道面試題:
#import "ViewController.h"
#import "NSObject+Block.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
- (void)viewDidLoad {
[super viewDidLoad];
[self blockDemo2];
}
- (void)blockDemo2 {
int a = 10;
void(^__weak block1)(void) = nil;
// {}里的代碼塊會(huì)立馬執(zhí)行
{
// 棧block2
void(^__weak block2)(void) = ^{
NSLog(@"%d",a);
};
block1 = block2;
int b = 5;
NSLog(@"%p", &c);
}
// 此時(shí)出了{(lán)...}的作用域b會(huì)被釋放嗎?
block1(); // 10
}
@end
此時(shí)的block1();會(huì)打印10。
此時(shí)出了{...}的作用域b會(huì)被釋放嗎? 不會(huì)。
因?yàn)閧}的一整塊代碼需要出了blockDemo2方法作用域才會(huì)被釋放。

- 第2.3道面試題:
此時(shí)我把blockDemo2再修改一下
- (void)blockDemo3{
int a = 10;
void(^__weak block1)(void) = nil;
{
// 棧block
void(^__weak block2)(void) = ^{
NSLog(@"%d",a);
};
block1 = [block2 copy];
}
block1();
}
此時(shí)block1();會(huì)崩潰!為什么?
因?yàn)?code>[block2 copy];是返回的結(jié)果是堆內(nèi)存的block,同時(shí)block1去弱引用這塊堆內(nèi)存并不會(huì)發(fā)生引用計(jì)數(shù)+1的情況(堆的內(nèi)存管理是通過(guò)引用計(jì)數(shù)的),所以block1在出{...}作用域的時(shí)候,ARC會(huì)自動(dòng)地[block1 release],導(dǎo)致block1被釋放。
要解決上面的問(wèn)題,需要一個(gè)強(qiáng)引用的方式去引用[block2 copy];
void(^block1)(void) = nil;
-
第2.4道面試題:
此時(shí)我再把blockDemo2再修改一下
- (void)blockDemo4 {
NSObject *objc = [NSObject new];
void(^__weak block1)(void) = nil;
{
void(^__weak block2)(void) = ^{
NSLog(@"%@", objc);
};
block1 = block2;
block1();
}
block1();
}
{...}里的block1()會(huì)打印objc的地址,block1會(huì)打印null
<NSObject:0x600000060590>
(null)
因?yàn)樵?code>{...}里棧block2捕獲的objc是在堆區(qū),并且objc的引用計(jì)數(shù)不會(huì)改變,在出{...}后objc被ARC自動(dòng)釋放[objc release]。
將blockDemo4方法進(jìn)行如下修改
- (void)blockDemo4{
NSObject *objc = [NSObject new];
void(^block1)(void) = nil;
{
void(^__weak block2)(void) = ^{
NSLog(@"%@", objc);
};
block1 = [block2 copy];
block1();
}
block1();
}
Block與內(nèi)存布局相關(guān)面試題
面試題:Block中可以修改全局變量,全局靜態(tài)變量,局部靜態(tài)變量,局部變量 嗎?
可以修改全局變量,全局靜態(tài)變量。因?yàn)槿肿兞?和 靜態(tài)全局變量是全局的,作用域很廣。可以修改局部靜態(tài)變量,不可以修改局部斌量。
1.局部靜態(tài)變量(static修飾的) 和局部變量,被block從外面捕獲,成為__main_block_impl_0這個(gè)結(jié)構(gòu)體的成員變量;
2.局部靜態(tài)變量是以指針形式被block捕獲的,由于捕獲的是指針,所以可以修改局部靜態(tài)變量的值;
3.局部變量是以值方式傳遞到block的構(gòu)造函數(shù),只會(huì)捕獲block中會(huì)用到的變量,由于只捕獲了變量的值,并非內(nèi)存地址,所以在block內(nèi)部不能改變局部變量的值。ARC環(huán)境下,一旦使用__block修飾并在block中修改,就會(huì)觸發(fā)copy,block就會(huì)從棧區(qū)copy到堆區(qū),此時(shí)的block是堆區(qū)block。ARC模式下
1.block中引用id類(lèi)型的數(shù)據(jù),無(wú)論有沒(méi)有__block修飾,都會(huì)retain。
2.block中引用基礎(chǔ)數(shù)據(jù)類(lèi)型的數(shù)據(jù),沒(méi)有__block就無(wú)法修改變量值;如果有__block修飾,也是在底層修改__Block_byref_a_0結(jié)構(gòu)體,將其內(nèi)部的forwarding指針指向copy后的地址,來(lái)達(dá)到值的修改。