block幾乎天天都在使用,也是面試題高發(fā)區(qū)。可是原理還是有點(diǎn)晦澀的,現(xiàn)在就靜下心來聽我慢慢道來!
1、循環(huán)引用的解決
1.1 循環(huán)引用的造成
-
正常情況:
-
循環(huán)引用:
- 兩個(gè)對象相互引用,導(dǎo)致兩個(gè)對象即使使用完成后也不能正常進(jìn)行
dealloc
,這就是循環(huán)引用
;
- Block的循環(huán)引用:
self.block = ^{
[self sayHello];
};
- Block是極其容易造成循環(huán)引用的,例如下方這段代碼。
self和block相關(guān)引用
;
1.2 循環(huán)引用的解決
解決循環(huán)引用常見的方式有以下幾種:
-
__weak + __strong + dance
,利用中介者模式; -
__block修飾引用對象
,同樣是利用中介者模式但是需要手動釋放引用對象; -
self作為參數(shù)傳入
; - 使用
NSProxy
;
1.2.1 __weak + __strong + dance
@property(nonatomic, copy) void(^block)(void);
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.name);
};
- 使用__weak修飾變量之后,
weakSelf和self是指向同一片內(nèi)存空間的
;weak這部分可以在OC底層探索19-weak和assign區(qū)別淺談有詳細(xì)的分析; - 這是最常見的一種處理方式;
1.2.2 __block修飾引用對象
@property(nonatomic, copy) void(^block)(void);
__block UIViewController *vc = self;
self.block = ^(void){
NSLog(@"%@",vc.name);
vc = nil; //手動釋放
};
- 在Block中對引用參數(shù)進(jìn)行修改必須使用
__block
修飾,第四部分會分析; - 需要注意的是:
block必須調(diào)用,不調(diào)用依舊會造成循環(huán)引用
;
1.2.3 self作為參數(shù)傳入
@property(nonatomic, copy) void(^block)(UIViewController *);
self.block = ^(UIViewController *vc){
NSLog(@"%@",vc.name);
};
//調(diào)用
self.block(self);
- 這種寫法在
ReactCocoa
中還挺常見的;
1.2.4 NSProxy 模板類
NSProxy 和 NSObject是同級的一個(gè)類,也可以說是一個(gè)虛擬類,只是實(shí)現(xiàn)了NSObject的協(xié)議;
@interface HRTestProxy : NSProxy
@property (nonatomic, strong, nullable) NSObject *target;
@end
@implementation HRTestProxy
// NSProxy實(shí)現(xiàn)邏輯和方法慢速轉(zhuǎn)發(fā)一致,通過模板類對詳細(xì)進(jìn)行轉(zhuǎn)發(fā)
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
-
OC底層探索12-消息動態(tài)決議,方法慢速、快速轉(zhuǎn)發(fā)的快速轉(zhuǎn)發(fā)部分對
methodSignatureForSelector
,forwardInvocation
這兩個(gè)方法有詳細(xì)的說明;
-(void)demo{
HRTestProxy *myProxy = [HRTestProxy alloc];
myProxy.target = self;
self.block = ^(void){
[myProxy performSelector:@selector(say)];
};
//調(diào)用
self.block();
}
-(void)say{
NSLog(@"%@",self.name);
}
- 通過利用虛基類
NSProxy
的特性將self -> block -> self
,改為self -> block -> NSProxy
的結(jié)構(gòu),從而完成循環(huán)引用的打破;
2、Block結(jié)構(gòu)分析
2.1 block最基本形態(tài)
分析結(jié)構(gòu)一定是離不開clang
,
clang命令:
clang -rewrite-objc main.m -o main.cpp
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block");
};
將上方代碼進(jìn)行編譯;
- block在編譯之后是一個(gè)
結(jié)構(gòu)體/對象
,isa:_NSConcreteStackBlock
和objc_object
是一致的; - 初始化:block結(jié)構(gòu)體的構(gòu)造函數(shù):
&__main_block_impl_0(方法, &參數(shù))
,將isa、大小參數(shù)、執(zhí)行函數(shù)等信息賦值; - 調(diào)用:從結(jié)構(gòu)體
__main_block_impl_0
里獲取FuncPtr
函數(shù)指針,也就是__main_block_func_0(block自己)
,并將block自己
傳入;這個(gè)位置可以理解為OC方法中的隱藏參數(shù): (id self,Sel cmd)
2.1 補(bǔ)充 block三種類型
__NSGlobalBlock__
全局block,存儲在全局區(qū)
void(^block)(void) = ^{
NSLog(@"HR");
};
- 此時(shí)的block無參也無返回值,屬于全局block
__NSMallocBlock__
堆區(qū)block
int a = 10;
void(^block)(void) = ^{
NSLog(@"HR - %d", a);
};
- 此時(shí)的block會訪問外界變量,即底層拷貝a,所以是堆區(qū)block
__NSStackBlock__
棧區(qū)block
int a = 10;
void(^__weak block)(void) = ^{
NSLog(@"HR - %d", a);
};
- 通過__weak不進(jìn)行強(qiáng)持有,block就還是棧區(qū)block;
2.2 捕獲外界變量(值拷貝
)
int a = 10;
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block - %d",a);
};
mallocBlock();
-
block內(nèi)部使用到了外部變量之后,會導(dǎo)致block結(jié)構(gòu)體有什么變化呢?;
初始化:發(fā)現(xiàn)
__main_block_impl_0
結(jié)構(gòu)體內(nèi)多了一個(gè)int a
,也就是說block會把捕獲的外界變量進(jìn)行copy到自身結(jié)構(gòu)里
,當(dāng)然這些操作都是編譯器幫我們完成了;調(diào)用沒有變化,只是在體內(nèi)多了一個(gè)a的取值.通過觀察在
__main_block_func_0
里參數(shù)int a = __cself->a;
進(jìn)行了值拷貝
;
2.1.1 值拷貝驗(yàn)證
- block外部a的地址;
- block內(nèi)部的a的地址,
兩個(gè)地址完全是不同
.驗(yàn)證了之前值拷貝的說法;
2.3 __block聲明變量(指針拷貝
)
在2.2中出現(xiàn)了值拷貝,而且在block中也不允許使用修改捕獲參數(shù),想過要block內(nèi)部修改就需要__block
聲明變量。
__block int a = 10;
void (^mallocBlock)(void) = ^void {
NSLog(@"HR_Block - %d",a++);
};
編譯之后block結(jié)構(gòu)體的變化很大;
- 被
__block
修改的被捕獲變量被聲明成__Block_byref_a_0
這樣的一個(gè)結(jié)構(gòu)。把指針的地址和值進(jìn)行保存,所以才可以在block塊內(nèi)完成值的修改
; - 調(diào)用:
__Block_byref_a_0 *a = __cself->a;
中的賦值和直接捕獲是不一樣的,這里是引用了__Block_byref_a_0
的地址。在a++
操作的時(shí)候(a->__forwarding->a)++)
;因?yàn)樵诔跏蓟臅r(shí)候保存的就是_a->__forwarding
; -
_Block_object_assign
,_Block_object_dispose
就是對外界捕獲參數(shù)的操作,在block的三層拷貝的時(shí)候會著重分析;這里就需要簡單知道:block需要對外界變量進(jìn)行持有;因?yàn)檫@里都是c++的操作,無法操作ARC完成引用計(jì)數(shù)的操作; - 如果__block 修飾的是一個(gè)對象的話:
__Block_byref_id_object_copy_xx
會出現(xiàn)這樣一個(gè)方法.
3、block模板類型
源碼位置:
在工程中增加符號斷點(diǎn)_Block_copy
。
libclosure源碼下載想要繼續(xù)深入就需要分析源碼;
3.1 Block_layout
Block真正的結(jié)構(gòu)類型,之前看到__main_block_impl_0
都是以它為模板生成的,所以Block_layout
是一個(gè)模板類型;
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
// 所有捕捉的外部函數(shù)聲明
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
// 涉及到__block修飾的外部變量時(shí)會出現(xiàn)
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; // 保存block的方法簽名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
3.1 Block_layout結(jié)構(gòu)圖:
3.2 __block修飾后Block_byref模板
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
//__Block 修飾的結(jié)構(gòu)體 byref_keep 和 byref_destroy 函數(shù) - 來處理里面持有對象的保持和銷毀
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
- 在前面出現(xiàn)的
__Block_byref_a_0
就是以Block_byref
為模板進(jìn)行創(chuàng)建的。 - 如果__block引用的是一個(gè)
對象
則會出現(xiàn)Block_byref_2
中的這部分結(jié)構(gòu)。
3.3 Block的方法簽名
利用Block_layout結(jié)構(gòu)獲取Block方法簽名篇幅有點(diǎn)大,就新起了一篇;代碼很有意思?xì)g迎閱讀!!
4、Block的多次拷貝
編譯器是不可以直接在堆區(qū)進(jìn)行創(chuàng)建的需要進(jìn)行malloc的內(nèi)存申請,可是__NSMallocBlock__
這個(gè)類型的Block就是存儲在堆區(qū)的,那么就一定進(jìn)行過malloc操作
;
4.1 _Block_copy
-lldb調(diào)試
還記得之前提到過的_Block_copy
這個(gè)符號斷點(diǎn)嗎?
- 通過
lldb獲取寄存器rax(模擬器)的值
,可以看到當(dāng)前是一個(gè)__NSStackBlock__
;
等到方法執(zhí)行完之后,在查看寄存器rax(模擬器)的值
- 進(jìn)過
_Block_copy
函數(shù)之后,Block從__NSStackBlock__
變成了一個(gè)__NSMallocBlock__
,這個(gè)函數(shù)就很關(guān)鍵了。
4.2 _Block_copy
-源碼 第一次拷貝
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// 這就是block第一次copy
aBlock = (struct Block_layout *)arg;
// 如果Block需要釋放則直接釋放
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果Block是一個(gè)全局Block,不做任何操作
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// 從堆區(qū)申請內(nèi)存空間,并將棧區(qū)內(nèi)容移動到堆區(qū)
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// 完成賦值、標(biāo)示符的修改
result->invoke = aBlock->invoke;
#endif
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 將Block的類型轉(zhuǎn)換為_NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
- 代碼很清晰,在
堆區(qū)申請空間
,并將棧區(qū)所有數(shù)據(jù)移動到堆區(qū)
; - 最后將isa指向
_NSConcreteMallocBlock
,完成類型的修改;
4.3 _Block_object_assign
-源碼 第二次拷貝
Block 捕獲外界變量的操作
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:
// _Block_retain_object沒有做任何處理
_Block_retain_object(object);
// Block對捕獲的外界參數(shù)進(jìn)行了一次強(qiáng)引用,這就是導(dǎo)致循環(huán)引用的本質(zhì)
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
// 如果外界參數(shù)是一個(gè)Block類型,那么再進(jìn)行一次_Block_copy
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// 外界參數(shù)被__block修飾
*dest = _Block_byref_copy(object);
break;
//還有些其他類型,這里就省略了
}
}
4.4 _Block_byref_copy
-源碼 第三次拷貝
__block 捕獲外界變量的操作 內(nèi)存拷貝 以及常規(guī)處理
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 申請了一片堆區(qū)控件
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block內(nèi)部持有的Block_byref 和 外界的Block_byref 所持有的對象是同一個(gè),這也是為什么__block修飾的變量具有修改能力
//copy 和 scr 的地址指針達(dá)到了完美的同一份拷貝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
//Block_byref_2是結(jié)構(gòu)體,__block修飾的可能是對象,對象通過byref_keep保存,在合適的時(shí)機(jī)進(jìn)行調(diào)用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
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);
copy3->layout = src3->layout;
}
//等價(jià)于 __Block_byref_id_object_copy_131
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 如果被
__block
修飾之后會進(jìn)入進(jìn)行第三次拷貝
,如果本次拷貝的變量又支持copy
那么將進(jìn)行更深層次的_Block_object_assign
;