block 三種類(lèi)型
全局block NSGlobalBlock
void (^block)(void) = ^{
NSLog(@"hahah");
};
NSLog(@"%@", block);
- 沒(méi)有對(duì)外界變量進(jìn)行捕獲的時(shí)候澎迎,它是個(gè)函數(shù)的區(qū)域硕舆,直接放在全局區(qū)寄悯,方便執(zhí)行調(diào)用。
堆區(qū)block NSMallocBlock
int a = 10;
void (^block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
- 訪問(wèn)外界變量的時(shí)候艰争,它將進(jìn)行一些處理坏瞄,因?yàn)樵L問(wèn)的變量可能在棧區(qū),堆區(qū)甩卓,如果在全局區(qū)鸠匀,去訪問(wèn)會(huì)比較麻煩,所以block進(jìn)行了相應(yīng)的copy。copy到了相應(yīng) 的一些區(qū)域逾柿。所以向上面進(jìn)行了一次強(qiáng)引用的時(shí)候缀棍。此時(shí)是堆block.
棧區(qū)block NSStackBlock
這里有個(gè)坑點(diǎn),在iOS14
之前 在block沒(méi)有進(jìn)行copy處理的時(shí)候它是一個(gè)棧區(qū)block机错,而之后卻放在了堆里爬范。
NSLog(@"%@",^{
NSLog(@"Cooci - %d",a);
});
- iOS14之前 為棧區(qū),也就是ARC下沒(méi)有被持有的話弱匪,向上面寫(xiě)法為棧區(qū)青瀑。
- 而現(xiàn)在在堆區(qū)。
棧區(qū)的block寫(xiě)法
int a = 10;
void (^__weak block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
- 引用了外部變量痢法,當(dāng)此時(shí)對(duì)block進(jìn)行了了一次弱引用它就在棧區(qū)狱窘。
block 循環(huán)引用
正常釋放
當(dāng) A 對(duì)象 持有 B對(duì)象 的時(shí)候,B對(duì)象 的引用計(jì)數(shù) 會(huì)+1
當(dāng)A釋放的時(shí)候會(huì)給 B 信號(hào)财搁,B接收到 release信號(hào)蘸炸,引用計(jì)數(shù) -1 等于0的時(shí)候 b的dealloc就會(huì)被調(diào)用
循環(huán)引用
當(dāng) A 持有 B ,B也持有 A 尖奔,你中有我 我中有你的情況搭儒。就會(huì)造成循環(huán)引用。
循環(huán)引用代碼示意
///會(huì)發(fā)生循環(huán)引用
self.block = ^(void){
NSLog(@"%@",self.name);
};
///不會(huì)發(fā)生循環(huán)引用
[UIView animateWithDuration:0.2 animations:^{
NSLog(@"%@",self.name);
}];
- 上面的情況我們都知道提茁,對(duì)于發(fā)生循環(huán)引用 我們?cè)撛趺唇鉀Q呢淹禾?
解決打破循環(huán)引用。
1茴扁、__weak typeof(self)weakSelf = self
__weak typeof(self)weakSelf = self
self.block = ^(void){
NSLog(@"%@", weakSelf.name);
};
- 這種情況我們都知道用這個(gè)方法來(lái)打破那為什么铃岔?
- 沒(méi)有打破之前 的樣子是這樣的
self
->block
->self
(self
持有block
,block
持有self
) ,
而打破之后 就是這樣self
->block
->weakSelf
->self
(self
持有block
,block
持有weakSelf
,weakSelf
持有self
.)那這樣就不會(huì)導(dǎo)致循環(huán)引用了么?weakSelf也持有者 self呢呀 - 因?yàn)? weakSelf 是弱引用表中的峭火,和當(dāng)前的self是同一個(gè)指針地址毁习。__weak并不會(huì)導(dǎo)致self的引用計(jì)數(shù)發(fā)生變化。
那這樣就沒(méi)問(wèn)題了嗎看下面
__weak typeof(self) weakSelf = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.block();
此時(shí)我們發(fā)現(xiàn) 當(dāng)前頁(yè)面確實(shí)走了 dealloc卖丸。但是 當(dāng) 延時(shí)任務(wù)回來(lái)的時(shí)候 纺且,卻發(fā)現(xiàn) 打印的為nil. 雖說(shuō)一個(gè)打印任務(wù)并無(wú)商大雅。但是當(dāng)里面執(zhí)行的任務(wù)為很重要的時(shí)候稍浆。我還沒(méi)走完你就 dealloc载碌,顯然不符合我的要求猜嘱。所以我們正確的用法為 weak - strong -Dance
強(qiáng)弱共舞,保證self的聲明周期。
__weak typeof(self) weakSelf = self;
self.block = ^(void){
// 時(shí)間 - 精力
// self 的生命周期
__strong __typeof(weakSelf)strongSelf = weakSelf; // 可以釋放 when
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
- 在這里我們可以看到 __weak 打破了循環(huán)引用嫁艇。
- __strong 延長(zhǎng)了 self的生命周期朗伶。
2丹允、通過(guò)傳參的形式將self 傳進(jìn)block任務(wù)中雌桑。
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
- 這樣的話就打破了block對(duì)vc的持有,此時(shí)vc是已傳參的形式悦昵,它在block里就相當(dāng)于一個(gè)臨時(shí)變量被壓棧進(jìn)來(lái)歧斟。
3、主動(dòng)打破循環(huán)
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
- __block 為了可以在block里可以進(jìn)行修改偏形。
- vc = nil 是為了打破循環(huán)引用静袖。
- 注意:此時(shí)不調(diào)用循環(huán)引用依舊會(huì)存在。
4俊扭、NSProxy 也可以,這里就不講了队橙,自行搜索。
底層探究
定義一個(gè)簡(jiǎn)單的.c文件 如下 萨惑;
int main(){
void(^block)(void) = ^{
printf("LG_Cooci");
};
//block();
return 0;
}
- clang 查看 底層被編譯成了什么樣
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
int main(){
///簡(jiǎn)化去掉返回值類(lèi)型
void(*block)(void) =
&__main_block_impl_0 ( __main_block_func_0 , &__main_block_desc_0_DATA ) ;
return 0;
}
- 清晰的看到一個(gè)函數(shù)
__main_block_impl_0
和兩個(gè)參數(shù) 參數(shù)1:__main_block_func_0
參數(shù)2:__main_block_desc_0_DATA
查看 __main_block_impl_0
這個(gè)函數(shù)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到
__main_block_impl_0
這個(gè)函數(shù)它是一個(gè)結(jié)構(gòu)體捐康,也就是說(shuō)block是一個(gè)__main_block_impl_0
類(lèi)型的對(duì)象。-
里面有兩個(gè) 結(jié)構(gòu)體成員 一個(gè)為
__block_impl
一個(gè)為__main_block_desc_0
類(lèi)型
__block_impl
結(jié)構(gòu)類(lèi)型struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
__main_block_desc_0
結(jié)構(gòu)類(lèi)型static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("LG_Cooci"); } 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)};
-
在看一下它(
__main_block_impl_0
)的構(gòu)造函數(shù)庸蔼。- 給第一個(gè)結(jié)構(gòu)體
__block_impl
類(lèi)型的 成員impl
賦值解总。
impl
的 isa成員賦值 為 棧block 類(lèi)型;
impl
的Flages
設(shè)置了個(gè)標(biāo)記姐仅;
impl
的FuncPtr
成員 賦值為外界 傳進(jìn)來(lái)的__main_block_func_0
函數(shù)花枫。 - 給第二個(gè)成員
Desc
賦值外界傳進(jìn)來(lái)的__main_block_desc_0_DATA
的地址。
- 給第一個(gè)結(jié)構(gòu)體
-
畫(huà)圖表示一下這個(gè)結(jié)構(gòu)
截屏2020-11-28 上午11.16.49.png 總結(jié): block的本質(zhì) 是一個(gè)結(jié)構(gòu)體 也可以說(shuō)是一個(gè)對(duì)象掏膏,它內(nèi)部有兩個(gè)屬性劳翰,一個(gè)來(lái)存放 塊任務(wù)的,方法 及設(shè)置當(dāng)前塊任務(wù)類(lèi)型的isa馒疹。另一個(gè)屬性來(lái)計(jì)算當(dāng)前自己結(jié)構(gòu)體所占空間大小佳簸。
下面 我們看一下block是如何發(fā)起調(diào)用的。
依舊是這段代碼颖变,打開(kāi) 下方的 block()調(diào)用生均。
int main(){
void(^block)(void) = ^{
printf("LG_Cooci");
};
block();
return 0;
}
clang 編譯期源碼
void(*block)(void) =
&__main_block_impl_0 ( __main_block_func_0 , &__main_block_desc_0_DATA ) ;
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
看到這里 我們就明白了,此時(shí)發(fā)起調(diào)用悼做,它是 將 block指針強(qiáng)轉(zhuǎn)為__block_impl
類(lèi)型疯特。并獲取之前存入的 FuncPtr 發(fā)起函數(shù)調(diào)用,并將 block指針作為參數(shù)傳入肛走。
block如何捕獲外界變量的
int main(){
int a =10;
void(^block)(void) = ^{
printf("LG_Cooci%d",a);
};
block();
return 0;
}
clang
int main(){
int a =10;
void(*block)(void) = &__main_block_impl_0 (
__main_block_func_0,
&__main_block_desc_0_DATA,
a
) ;
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
- 此時(shí)我們看到 當(dāng)block內(nèi)部引用到了外部變量的時(shí)候漓雅。
__main_block_impl_0 構(gòu)造函數(shù)
就會(huì)動(dòng)態(tài)的向后添加一個(gè) 參數(shù)。
再次看下__main_block_impl_0
結(jié)構(gòu)體變化
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__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;
}
};
- 可以看到
__main_block_impl_0結(jié)構(gòu)體
中多了一個(gè)int 類(lèi)型的 a. 通過(guò)構(gòu)造函數(shù) 將a賦值。
再次 看 __block_impl
的 FuncPtr
賦值 也就是外界傳進(jìn)來(lái)的 __main_block_func_0
函數(shù)實(shí)現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("LG_Cooci%d",a);
}
此時(shí)我們可以看到 當(dāng)block發(fā)起調(diào)用的時(shí)候 此時(shí) 將
__main_block_func_0結(jié)構(gòu)體
中的a的值 賦值給了一個(gè)臨時(shí)變量 邻吞。由此就可以下結(jié)論组题,此時(shí)是值拷貝,外界a的變化 并不會(huì) 引起 block內(nèi)部 a的變化抱冷。
為了徹底弄清楚 我們 寫(xiě)一個(gè)我們平常的oc 對(duì)象,在block塊內(nèi)部引用
請(qǐng)問(wèn)下面輸出什么崔列?
LGPerson * person = [[LGPerson alloc]init];
person.tag = @"等風(fēng)來(lái)不如追風(fēng)去,總有那么一個(gè)人在風(fēng)景正好的季節(jié)來(lái)到你的身邊";
void(^block)(void) = ^{
NSLog(@"%@",person.tag);
};
person.tag = @"45°仰望天空旺遮,該死我那無(wú)處安放的魅力";
block();
我們 clang 去看
LGPerson * person = (((void *)objc_msgSend)((id)((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setTag:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_0);
///block 構(gòu)造函數(shù) 結(jié)構(gòu)體賦值
void(*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
person,
570425344));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setTag:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_2);
///發(fā)起調(diào)用
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
- 此時(shí)我們看到 此時(shí)
__main_block_impl_0
將person指針
捕獲進(jìn)去了赵讯。
再次 看此時(shí)的 __main_block_impl_0結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LGPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,LGPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 可以看到結(jié)構(gòu)體內(nèi)部已經(jīng)多了一個(gè) 對(duì)象指針。
在看一下方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
LHPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_6caa76_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("tag")));
}
- 當(dāng)block 發(fā)起調(diào)用的時(shí)候耿眉,首先找到 impl中存入的 方法边翼,并用此方法發(fā)起調(diào)用,并將 block的結(jié)構(gòu)體對(duì)象 當(dāng)參數(shù)鸣剪,傳入组底。此時(shí)可以看到 獲取了 block結(jié)構(gòu)體中的 person指針。
- 到現(xiàn)在我們明白了 此時(shí)對(duì)象類(lèi)型的捕獲的是對(duì)象的指針筐骇。屬于指針copy债鸡。也就是淺拷貝。
值copy 此時(shí) 內(nèi)存空間 兩個(gè)一樣的 內(nèi)容铛纬。指針 不一樣厌均。也就是深拷貝。
指針 copy 此時(shí) copy了一個(gè)指針饺鹃,兩個(gè)指針指向同一片內(nèi)存區(qū)域莫秆。也就是淺拷貝。
我們對(duì)于 值拷貝的基礎(chǔ)數(shù)據(jù)類(lèi)型的捕獲 該如何操作呢悔详?
__block
在什么情況下我們需要用__block的修飾镊屎?
- 當(dāng)block內(nèi)部需要對(duì)外界的變量 修改時(shí),如不用__block修飾茄螃,會(huì)引起編譯器的歧義缝驳,導(dǎo)致只能讀。
- 當(dāng)捕獲的是臨時(shí)變量归苍,如不用__block修飾用狱,會(huì)導(dǎo)致內(nèi)外數(shù)據(jù)不同步。
- 如捕獲的是容器類(lèi)型拼弃,容器內(nèi)容發(fā)生更改不需要進(jìn)行__block修飾夏伊。
- 如捕獲的是對(duì)象,對(duì)象的某個(gè)屬性發(fā)生更改吻氧,不需要進(jìn)行__block修飾溺忧。
- 如捕獲的是 statc修飾的(局部 /全局)變量 或 全局變量 不需要__block修飾咏连。
__block又做了哪些事情?帶著疑問(wèn)向下分析
int main(){
__block int a =10;
void(^block)(void) = ^{
printf("LG_Cooci%d",a);
};
a = 20;
block();
return 0;
}
繼續(xù) clang看編譯期變成了什么樣
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
===============================================
__Block_byref_a_0 a = {
0,
(__Block_byref_a_0 *) &a,
0,
sizeof(__Block_byref_a_0),
10
};
void(*block)(void) = &__main_block_impl_0 (
__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_a_0 *)&a,
570425344
);
(a.__forwarding->a) = 20;
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
此時(shí)我們看到了
變量 a
被包裝成了 一個(gè)__Block_byref_a_0
類(lèi)型的結(jié)構(gòu)體對(duì)象,并相對(duì)應(yīng)的錄入變量a
的信息鲁森, 對(duì)應(yīng)上面結(jié)構(gòu)體可以清楚的看到 里面存有a的地址
賦值給__forwarding
指針祟滴,a的值
,自身大小
等參數(shù)歌溉。將這個(gè)包裝后的
a的結(jié)構(gòu)體對(duì)象取地址
垄懂, 作為block結(jié)構(gòu)體
的構(gòu)造函數(shù)__main_block_impl_0
參數(shù)傳入 賦值給 block結(jié)構(gòu)體里邊的a指針
。調(diào)用執(zhí)行上一行代碼 拿到
a結(jié)構(gòu)體指針修改
a變量的值痛垛。所以內(nèi)外同步數(shù)據(jù)草慧。
繼續(xù)查看 block結(jié)構(gòu)體__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__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;
}
};
查看 __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("LG_Cooci%d",(a->__forwarding->a));
}
- 這里清晰的看到獲取到block結(jié)構(gòu)體里面的包裝后的
__Block_byref_a_0
類(lèi)型的 a指針,并通過(guò) a指針拿到__forwarding
也就是 指向外界變量的a地址的指針匙头,并取出 變量a真正的值冠蒋。 - 這里從不加__Block的值拷貝 變成了 指針拷貝。而這個(gè)指針是指向的同 一個(gè)結(jié)構(gòu)體地址乾胶,這個(gè)結(jié)構(gòu)體里面存有 變量 a的地址 和a的值,
咦朽寞?那為啥數(shù)據(jù)就同步了呢识窿,我不用__Block修飾 我捕獲一個(gè)字符串,它也是指針那為啥 當(dāng)我在對(duì)block發(fā)起調(diào)用前重新修改 字符串的值脑融,它怎么數(shù)據(jù)不同步呢喻频?
NSString * str = [NSString stringWithFormat:@"等風(fēng)來(lái)不如追風(fēng)去啊"];
void (^block)(void) = ^{
NSLog(@"%@,%p",str,str);
};
str = @"總有一個(gè)人,在風(fēng)景正好的季節(jié)等著你";
NSLog(@"%@,%p",str,str);
block();
- 看著上面的疑問(wèn) 在次陷入深思,我們繼續(xù)看下clang之后的編譯期代碼
///字符串指針
NSString * str = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_0);
/// block
void (*block)(void) =
__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
str,
570425344
);
///重新賦值 改變指針指向
str = (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_2;
/// 打印
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_3,str,str);
///發(fā)起調(diào)用
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
看func函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSString *__strong str = __cself->str; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_e485f6_mi_1,str,str);
}
}
- 看到這里的的確確是指針拷貝肘迎,func函數(shù)里的指針指向 和 block析構(gòu)函數(shù)參數(shù)指針指向同一片地址空間甥温。
- 那為啥 我在調(diào)用之前更改了 str變量的值里邊它里邊不同步?
繼續(xù)帶著這個(gè)疑問(wèn) 我們打印一下上下str的指針指向地址妓布。
- 雖然是捕獲的是指針姻蚓,在調(diào)用之前 指針的指向被改變,它指向了新的一片地址空間 匣沼。
用__block
修飾 運(yùn)行
- 此時(shí)我們知道 str被封裝為一個(gè)
結(jié)構(gòu)體對(duì)象
- 而在調(diào)用block之前進(jìn)行進(jìn)行對(duì) str修改狰挡,此時(shí)為結(jié)構(gòu)體
指針copy
。 對(duì)它指向的這個(gè)結(jié)構(gòu)體地址里的 str的值所占用的內(nèi)存空間進(jìn)行了修改释涛。所以數(shù)據(jù)同步加叁。
為了驗(yàn)證我們的想法 再次查看用block修飾后的cpp
struct __Block_byref_str_0 {
void *__isa;
__Block_byref_str_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *__strong str;
===============================================
///byref結(jié)構(gòu)體對(duì)象
__Block_byref_str_0 str = {
(void*)0,
(__Block_byref_str_0 *)&str,
33554432,
sizeof(__Block_byref_str_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_0)
};
/// block
&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_str_0 *)&str,
570425344
);
///重新賦值
(str.__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_2;
///打印
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_3,(str.__forwarding->str),(str.__forwarding->str));
///函數(shù)調(diào)用
(__block_impl *)block)->FuncPtr)((__block_impl *)block);
- 咦這里好像和基本數(shù)據(jù)類(lèi)型 int a的包裝還不太一樣多了兩個(gè)函數(shù) __Block_byref_id_object_copy 和__Block_byref_id_object_dispose
- 我們重新賦值是改變的 包裝后的結(jié)構(gòu)體中的 str指針。
在看一下 func
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_str_0 *str = __cself->str; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hp_8p1s5vl9501d23q4rjltk8j80000gn_T_main_1c411f_mi_1,(str->__forwarding->str),(str->__forwarding->str));
}
- 從這里我們就能看出 此時(shí)是byref結(jié)構(gòu)體指針copy 通過(guò)指針獲取 forwarding 地址在取得 str指針指向唇撬。
總結(jié)
- 捕獲的外界變量 底層會(huì)包裝成一個(gè) __Block_byref_a_0類(lèi)型的結(jié)構(gòu)體它匕。
- 結(jié)構(gòu)體用來(lái)保存 原始的變量的指針 和值。
- 將包裝的成的結(jié)構(gòu)體對(duì)象地址 傳遞 給block ,然后block內(nèi)部就可以對(duì)外界變量進(jìn)行操作窖认。
- 但是其內(nèi)部是到底是怎么操作的為什么 string對(duì)象類(lèi)型 要比 int基本數(shù)據(jù)類(lèi)型 byref會(huì)多出兩個(gè)方法豫柬?帶著這些疑問(wèn)向下看告希。
block真正的類(lèi)型
打開(kāi)匯編,并在下面區(qū)域打上斷點(diǎn)
運(yùn)行
我們看到到了callq
了 幾個(gè)很重要的函數(shù) 一個(gè)
- objc_retainBlock
- objc_storeStrong
- _Block_object_dispose
分別符號(hào)斷點(diǎn)下這個(gè) 看他來(lái)自哪個(gè)"
星球"
斷點(diǎn) objc_retainBlock
- 看到重要線索 它來(lái)自
libobjc
, 并其實(shí)真正調(diào)用的是_Block_copy
; - 那還等什么去源碼看看轮傍。
objc4源碼全局搜索 objc_retainBlock
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
- 嗯沒(méi)毛病 的確調(diào)用的 是 _Block_copy;
全局搜索 _Block_copy
發(fā)現(xiàn)Objc并未發(fā)現(xiàn)什么
那接著下符號(hào)斷點(diǎn)吧它肯定不來(lái)自這個(gè)庫(kù)了暂雹。
- 原來(lái)它來(lái)自 libsystem_blocks.dylib。
官網(wǎng)找到開(kāi)源庫(kù)全局搜索 _Block_copy
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
// block都是`Block_layout`類(lèi)型
struct Block_layout *aBlock;
// 沒(méi)有內(nèi)容创夜,直接返回空
if (!arg) return NULL;
// The following would be better done as a switch statement
// 將內(nèi)容轉(zhuǎn)變?yōu)閌Block_layout`結(jié)構(gòu)體格式
aBlock = (struct Block_layout *)arg;
// 檢查是否需要釋放
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果是全局Block,直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
//
else {
// Its a stack block. Make a copy.
// 進(jìn)入的是棧區(qū)block杭跪,拷貝一份
// 開(kāi)辟一個(gè)大小空間的result對(duì)象
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 開(kāi)辟失敗,就返回
if (!result) return NULL;
// 內(nèi)存拷貝:將aBlock內(nè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驰吓。
result->invoke = aBlock->invoke;
#endif
// reset refcount
// BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING :前16位都為1
// ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING):前16位都為0
// 與操作涧尿,結(jié)果為前16位都為0 引用計(jì)數(shù)為0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 設(shè)置為需要釋放,引用計(jì)數(shù)為1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 生成desc檬贰,并記錄了result和aBlock
_Block_call_copy_helper(result, aBlock); //
// Set isa last so memory analysis tools see a fully-initialized object.
// 設(shè)置isa為堆區(qū)Block
result->isa = _NSConcreteMallocBlock;
return result;
}
}
- 這里我們看到了 block真正的類(lèi)型 原來(lái)它是
Block_layout
類(lèi)型的結(jié)構(gòu)體 - 仔細(xì)看 上面源代碼的幾個(gè)
if else
判斷
1姑廉、如果需要釋放的(堆是由程序員管理的) 也就是 堆block的,增加引用計(jì)數(shù) 返回
2翁涤、如果是全局的桥言,直接返回
3、如果是棧block.:從棧中 copy到 堆中葵礼; 過(guò)程:malloc開(kāi)辟空間
->memmove內(nèi)存拷貝
->invoke 指針拷貝
->flag引用計(jì)數(shù) 設(shè)置為1
->生成desc
->設(shè)置isa為堆block
->返回堆block.
查看 Block_layout
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
- isa : 從靜態(tài)分析 到 動(dòng)態(tài)庫(kù)我們都知道了号阿,它就是標(biāo)記為是什么類(lèi)型的block。
- flags: 標(biāo)識(shí)碼(每一位都有特殊含義)
- reserved : 保留字段
- invoke : block執(zhí)行函數(shù)(存儲(chǔ)執(zhí)行代碼塊)
- descriptor: Block詳細(xì)信息
查看 Flags:標(biāo)識(shí)碼
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- flags的賦值鸳粉。按bit位表示一些block的附加信息扔涧,類(lèi)似 isa中的位域,其中flags的種類(lèi)有上面幾種
查看 Block_descriptor_1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
// 可選
#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;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
- 這里我們看到了 這個(gè) 類(lèi)型擁3個(gè)結(jié)構(gòu)體樣式届谈。
- 可選類(lèi)型 descriptor 2 為BLOCK_HAS_COPY_DISPOSE
- 可選類(lèi)型 descriptor 3 為BLOCK_HAS_SIGNATURE
總結(jié)
- block真正的底層結(jié)構(gòu)為block_layout, 它里面包含 isa 枯夜,isa為最終確定的類(lèi)型。還有flag 艰山, 類(lèi)似 isa中的位域 湖雹。它里面記錄著當(dāng)前block的狀態(tài),如是否需要釋放曙搬,是否是global 劝枣,是否需要簽名進(jìn)行消息發(fā)送等。運(yùn)行時(shí)會(huì)調(diào)用block_copy织鲸,通過(guò)編譯期的flag判斷當(dāng)前block的類(lèi)型舔腾,如果是 需要釋放的 操作引用計(jì)數(shù)并返回,如是全局block不做任何操作返回搂擦,如果是棧區(qū)的block 需要將 棧區(qū)的block Copy 到堆上稳诚,(申請(qǐng)內(nèi)存空間 ,將棧區(qū)的block拷貝的堆區(qū) 瀑踢,將 block的執(zhí)行函數(shù) invoke拷貝扳还,重新設(shè)置 flages 類(lèi)型才避,生成對(duì)應(yīng)的 desc,設(shè)置 isa類(lèi)型為堆block) 此時(shí)block為最真實(shí)的狀態(tài)。
查看 _Block_call_copy_helper
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
- 這里可以看到 如果擁有拓展descriptor2那么會(huì)發(fā)起一個(gè)函數(shù)調(diào)用
查看descriptor訪問(wèn)操作
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
- 這里可以清晰的看到氨距,默認(rèn)獲取 block_layout 里 descriptor信息
- 根據(jù) block_layout里的flags& BLOCK_HAS_COPY_DISPOSE 如果為真 證明 有descriptor_2附加信息桑逝。 拿到 descripor1的指針 平移自身大小 得到 descriptor_2。
- 根據(jù) block_layout里的flags& BLOCK_HAS_SIGNATURE 如果為真 證明 有descriptor_3附加結(jié)構(gòu)體信息俏让。首先拿到 拿到 descripor1的指針 平移其自身大小 ,并查看是否有descriptor_2附加結(jié)構(gòu)體楞遏,如果有,那么在平移加上 decriptor2大小 ,最終得到 descriptor_3
看到這里我們應(yīng)該更能體會(huì)到 descriptor 屬性及上面的附加可選什么意思 下面畫(huà)個(gè)圖
以上為我們開(kāi)了上帝視角 下面我們實(shí)際操作 親眼所看到 從棧block 拷貝到堆的過(guò)程
上面我們已經(jīng)通過(guò)閱讀源碼知道了 當(dāng)?shù)讓诱{(diào)用完Block_copy 其真實(shí)的block類(lèi)型就會(huì)確定所以我們?cè)谡{(diào)用之前打斷點(diǎn)讀取
- 可以看到此時(shí)為 NSStackBlock
按住 ctrl + 鼠標(biāo)點(diǎn)擊 向下箭頭 首昔,跳進(jìn) objc_retainBlock 方法繼續(xù)打印
跳進(jìn)了 objc_retainBlock
- 此時(shí)可以看到 依舊為NSStackBlock 地址指針并沒(méi)有變化
打入objc_retainBlock的全局?jǐn)帱c(diǎn) 并繼續(xù)讀取
- 可以看到依舊沒(méi)有變化
按住 ctrl + 鼠標(biāo)點(diǎn)擊 向下箭頭 繼續(xù)向下走
- 此時(shí)可以看到清晰的它調(diào)用 libobjc庫(kù)的 objc_retainBlock方法
- 此時(shí) 依舊沒(méi)有變化
繼續(xù)跟進(jìn)跳轉(zhuǎn)
- 發(fā)現(xiàn)太長(zhǎng)了 那么這里我們只需要斷到其 返回值
- 此時(shí)此刻 它發(fā)生了變化寡喝。變成了 NSMallocBlock
- 這也就很清晰的看到了block是什么時(shí)候從棧block變?yōu)槎训摹?/li>
我們分析了block是如何確定最終類(lèi)型的,那還是不了解block是如何捕獲外界變量的勒奇,為什么__block修飾后 數(shù)據(jù)會(huì)同步呢预鬓? 下面我繼續(xù)分析 底層
先看圖
- 首先經(jīng)過(guò)這兩種類(lèi)型的__block我們發(fā)現(xiàn) 不同的地方就是修飾指針類(lèi)型的對(duì)象在byref包裝結(jié)構(gòu)體中會(huì)多出兩個(gè)函數(shù)。
- 共同地方是經(jīng)過(guò)__block修飾后在block_impl中的desc結(jié)構(gòu)體會(huì)多出兩個(gè)函數(shù)赊颠。
看到這里我們也許就更加明白了格二,還記的blockLayout結(jié)構(gòu)體中desc嗎?它的desc有可選的拓展結(jié)構(gòu)體竣蹦,是根據(jù) blockLayout里的flags&上 枚舉來(lái)確定是否擁有蟋定,在這里用__block修飾之后,它多出的這兩個(gè)函數(shù)正好和descriptor_2一一對(duì)應(yīng)草添。 - 他們底層調(diào)用的同屬 _Block_object_assign 和 _Block_object_dispose函數(shù)
源碼搜索 _Block_object_assign
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];
********/
// objc 指針地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一個(gè) block 捕獲
*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 = _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 = 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 = object;
break;
default:
break;
}
}
- 如果是普通對(duì)象,交給系統(tǒng)arc處理扼仲,并拷貝對(duì)象指針远寸,引用計(jì)數(shù)+1 ,外界變量不能釋放屠凶。
- 如果是block類(lèi)型的變量驰后,又會(huì)回到_Block_copy操作,將block從棧 拷貝到堆區(qū)矗愧。
- 如果是__block修飾的變量灶芝,調(diào)用_Block_byref_copy函數(shù),進(jìn)行內(nèi)存拷貝及處理唉韭。
查看 枚舉 值
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通對(duì)象夜涕,即沒(méi)有其他的引用類(lèi)型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block類(lèi)型作為變量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//經(jīng)過(guò)__block修飾的變量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用變量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的調(diào)用對(duì)象 - 處理block_byref內(nèi)部對(duì)象內(nèi)存會(huì)加的一個(gè)額外標(biāo)記,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
搜索 _Block_byref_copy
static struct Block_byref *_Block_byref_copy(const void *arg) {
//強(qiáng)轉(zhuǎn)為Block_byref結(jié)構(gòu)體類(lèi)型属愤,保存一份
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申請(qǐng)內(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
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block內(nèi)部持有的Block_byref 和 外界的Block_byref 所持有的對(duì)象是同一個(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) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是結(jié)構(gòu)體住诸,__block修飾的可能是對(duì)象驾胆,對(duì)象通過(guò)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
(*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包裝的變量,真實(shí)的類(lèi)型為Block_byref結(jié)構(gòu)體丧诺。
- 將棧上的Block_byref 結(jié)構(gòu)體拷貝到堆上入桂,根據(jù)大小申請(qǐng)內(nèi)存空間--> 設(shè)置isa為 Null ->設(shè)置flags信息
-> 設(shè)置堆上Block_byref結(jié)構(gòu)體的forwarding指針指向 為 自己->更改棧上Block_byref結(jié)構(gòu)體的forwarding 指針指向?yàn)槎焉系腂lock_byref ->設(shè)置堆byref的size大小 為 棧上的byref的size大小。 - 判斷如果有copy dispose,(這里我們?cè)谏厦嬲f(shuō)過(guò)驳阎,__block修飾的指針類(lèi)型抗愁,比基本數(shù)據(jù)類(lèi)型在包裝的結(jié)構(gòu)體中會(huì)多出來(lái)兩個(gè)函數(shù),此時(shí)和這里是一一對(duì)應(yīng)的),通過(guò)類(lèi)似上面獲取desc2 和desc3的方式搞隐,這里是偏移一個(gè)Block_byref 大小 拿到 src2也就是包含copy和dispose成員變量的Block_byref_2結(jié)構(gòu)體,來(lái)獲取 copy和dispose 函數(shù)并將其拷貝到堆中驹愚。 判斷如果有 layout成員變量,與獲取src2一樣的效果劣纲,這里是偏移一個(gè)Block_byref_2的大小來(lái)獲取src3 并將layout變量拷貝到堆上逢捺,也就是堆上Block_byref_3 的變量layout 指向棧中l(wèi)ayout。通過(guò)調(diào)用 byref_keep來(lái)實(shí)現(xiàn)響應(yīng)癞季,它就對(duì)應(yīng)外部的__Block_byref_id_object_copy
我們看一下 Block_byref 結(jié)構(gòu)體
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 結(jié)構(gòu)體 __block 對(duì)象
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
在看一下 byref中的flags的枚舉
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
- 此時(shí)可以看出 和Block_copy中的處理方式非常的相似
在_Block_byref_copy中我們看到src2->byref_keep劫瞳,其實(shí)就是調(diào)用外部的__Block_byref_id_object_copy_131,為什么绷柒?
這里我們 看 Block_byref_2 中兩個(gè)函數(shù) 志于,clang編譯器中的兩個(gè)函數(shù)
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 結(jié)構(gòu)體 __block 對(duì)象
BlockByrefDestroyFunction byref_destroy;
};
- __Block_byref_id_object_copy_131入?yún)⒗锩妫幸粋€(gè)內(nèi)存平移40废睦,
原因
- 因?yàn)?內(nèi)存偏移 40才能取到 NSstring*__strong str
而131 = 128 +3伺绽,其中128表示BLOCK_BYREF_CALLER --> 代表__block變量有copy/dispose的內(nèi)存管理輔助函數(shù)
我們這里示例的對(duì)象類(lèi)型為NSString,就表示上述枚舉中這個(gè) BLOCK_FIELD_IS_OBJECT
,也就是繼承NSObjcet類(lèi)型的 id類(lèi)型的 為3嗜湃,然后和copy函數(shù)拼接起來(lái)就是 __Block_byref_id_object_copy_131
所以在_Block_byref_copy 中以下標(biāo)紅出就相當(dāng)于 __Block_byref_id_object_copy_131的調(diào)用
而這里的調(diào)用又會(huì)觸發(fā) _Block_object_assign
總結(jié)
詳細(xì)總結(jié):
Block真正的底層是Block_layout 對(duì)象奈应,clang編譯器 會(huì)根據(jù)捕獲類(lèi)型,來(lái)動(dòng)態(tài)的改變购披,及生成對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)杖挣。如用__block修飾后的對(duì)象,clang編譯器會(huì)將其封裝為一個(gè)byref的結(jié)構(gòu)體對(duì)象刚陡,此結(jié)構(gòu)體對(duì)象在底層真正的類(lèi)型為 Block_byref 結(jié)構(gòu)體惩妇。
在運(yùn)行時(shí) 會(huì)調(diào)用Block_copy 函數(shù) 通過(guò) block_layout對(duì)象中的flags標(biāo)記 判斷當(dāng)前block的類(lèi)型及狀態(tài)。如果是需要釋放的 那么 只操作引用計(jì)數(shù)并返回筐乳,如果是全局block那么直接返回歌殃,如果是棧區(qū)的block, 開(kāi)辟內(nèi)存空間 蝙云,設(shè)置屬性為堆區(qū)的標(biāo)識(shí)及一些設(shè)置挺份。其中最具代表性的屬性為 desc ,在默認(rèn)情況下block的描述desc只有一個(gè)贮懈,當(dāng)被__block修飾之后 匀泊,clang編譯器會(huì)在desc結(jié)構(gòu)體中多出兩個(gè)函數(shù)copy/dispose 底層會(huì)根據(jù) block的flags 標(biāo)識(shí) 來(lái)判斷是否擁有 desc2 或者 desc3 的block的拓展信息优训,如判斷擁有 copy/dispose 函數(shù),那么會(huì)執(zhí)行copy函數(shù)此時(shí)會(huì)調(diào)用Block_object_assign函數(shù) 此函數(shù)中同樣的會(huì)判斷當(dāng)前捕獲的是什么類(lèi)型各聘,進(jìn)行不同的處理揣非, 此時(shí)是__block修飾的變量也就byref結(jié)構(gòu)體 將會(huì)掉起 _Block_byref_copy 函數(shù),此函數(shù)正是對(duì)byref結(jié)構(gòu)體 從棧中copy到堆中的操作躲因, 類(lèi)似block的copy早敬。首先開(kāi)辟內(nèi)存,設(shè)置 屬性為堆區(qū)的標(biāo)識(shí)及一些設(shè)置大脉,這里重要的操作為搞监,將堆區(qū)的forwarding指針 指向 堆區(qū)的Block_byref自己.將棧區(qū)的forwarding指針指向更改為堆區(qū)的Block_byref結(jié)構(gòu)體。并設(shè)置 棧區(qū)的大小镰矿。同樣根據(jù)棧區(qū)的byref標(biāo)識(shí)flags判斷是否支持 copy/和dispose函數(shù)琐驴,如果支持,通過(guò)指針平移獲取棧區(qū)堆區(qū)的 Block_byref2 拓展結(jié)構(gòu)體, 從棧區(qū)的這兩個(gè)函數(shù)指針賦值 堆區(qū)的 Block_byref2 中秤标。再此判斷中還判斷了是否支持layout拓展绝淡,如支持 同樣通過(guò)指針平移獲取棧區(qū)堆區(qū)的 Block_byref3拓展結(jié)構(gòu)體,從棧區(qū)的這個(gè)函數(shù)指針賦值 堆區(qū)的 Block_byref3 中.
如支持copy/dispose 函數(shù) 那么將再次發(fā)起 Block_object_assign函數(shù)調(diào)用苍姜,此時(shí)進(jìn)行的是通過(guò)Block_byref結(jié)構(gòu)體偏移獲取被修飾的指針變量進(jìn)行 指針copy 也就是引用計(jì)數(shù)+1
非太詳細(xì):
也就是 __block修飾的基本數(shù)據(jù)類(lèi)型會(huì)進(jìn)行 二次copy 一個(gè)是block的copy 一個(gè)是byref結(jié)構(gòu)體的copy 都是從 棧中 copy到堆中牢酵。
如果修飾的是指針類(lèi)型,那么會(huì)進(jìn)行三次 copy衙猪,前兩次和上面一樣馍乙,最后一次 會(huì)對(duì)修飾的原始指針,進(jìn)行 指針copy引用計(jì)數(shù)+1.