一兵钮、__block 的解析
接上一篇《iOS Block底層解析一》,在block中我們要修改局部變量(自動局部變量田柔,自動局部類對象)前面都加__block修飾 自動是auto的意思艾蓝,就是我們寫代碼蘋果自動給我們生成的 比如:int a = 10;其實是 auto int a = 10; 下面就直接說局部變量吧,這么麻煩的叫法膈應人
為啥局部變量在block里面不能直接修改粪牲,從上一篇的分析我們知道我們傳進去的局部變量古瓤,相當于函數(shù)的參數(shù),你在函數(shù)里面改參數(shù)腺阳,外面的局部變量會變嗎落君?例如:
void test8(int a) {
a++;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int b = 10;
test8(b);
NSLog(@"%d",b);
}
return 0;
}
打印結果:
2020-04-13 21:12:24.922403+0800 blockTest[3434:163752] 10
- 類對象的屬性修改要不要加__block呢? 其實不用的亭引,跟NSMutableArray等 增绎速、刪、改焙蚓、查是一樣的纹冤,我們修改的都是她里面的東西,其實是因為block最后拿到的還是person地址里面的name值 例如:
// 對象類型
void test6() {
Person *p = [Person new];
p.name = @"hello block!";
void(^block)(void) = ^{
p.name = @"fuck block!";
NSLog(@"--- %@",p.name);
};
block();
}
打印結果:
2020-04-13 21:18:06.198269+0800 blockTest[3486:167284] --- fuck block!
void test8() {
NSMutableArray * arr = [NSMutableArray new];
void(^block)(void) = ^{
[arr addObject:@2];
NSLog(@"--- %@",arr);
};
block();
}
打印結果:
2020-04-13 21:25:44.568467+0800 blockTest[3515:170553] --- (
2
)
-
但是我們改變他們本身就會報錯购公,例如:
圖片.png
圖片.png
一定要知會的一點就是有__block修飾的捕獲變量和沒有__block修飾的捕獲變量要分開萌京,內(nèi)存管理上不要混淆,這樣思路才更清晰君丁。這也是跟上一篇分開解析的原因
其實我們要加__block就兩種情況枫夺,局部變量和局部類對象,其他的都可以直接訪問在block中修改绘闷,廢話有點多 開搞 開搞
// __block作用
void test7() {
__block int age = 10;
__block NSObject *objc = [[NSObject alloc] init];
void(^block)(void) = ^ {
objc = nil;
age = 20;
};
block();
}
- 老套路clang一波
把轉換的去掉簡化代碼,擺好步驟:
// 第一步
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test7();
}
return 0;
}
// 第二步
void test7() {
// __block int age = 10;
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10
};
// __block NSObject *objc = [[NSObject alloc] init];
__Block_byref_objc_1 objc = {
0,
&objc,
33554432,
sizeof(__Block_byref_objc_1),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
objc_msgSend((id)(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
};
// 調(diào)用
void(*block)(void) = &__test7_block_impl_0(__test7_block_func_0, &__test7_block_desc_0_DATA, (__Block_byref_objc_1 *)&objc, (__Block_byref_age_0 *)&age, 570425344));
block->FuncPtr(block);
}
// 第三步
struct __Block_byref_age_0 {
void *__isa; // isa指針
__Block_byref_age_0 *__forwarding; // 指向自己的指針
int __flags; // 不清楚是什么鬼 猜測是標識符什么的
int __size; // 當前結構體的大小
int age; // 捕獲值
};
// 第三步
struct __Block_byref_objc_1 {
void *__isa; // isa指針
__Block_byref_objc_1 *__forwarding; // 指向自己的指針
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // 執(zhí)行copy操作
void (*__Block_byref_id_object_dispose)(void*); // 執(zhí)行dispose操作
NSObject *objc;// 捕獲 NSObject類型指針
};
// 第四步
static void __test7_block_func_0(struct __test7_block_impl_0 *__cself) {
// 拿到 __Block_byref_objc_1里面 *objc指針
__Block_byref_objc_1 *objc = __cself->objc; // bound by ref
// 拿到__Block_byref_age_0里面 *age指針
__Block_byref_age_0 *age = __cself->age; // bound by ref
//objc->__forwarding指針指向 __Block_byref_objc_1 拿到里面的的objc 完成賦值
(objc->__forwarding->objc) = __null;
//age->__forwarding指針指向 __Block_byref_age_0 拿到里面的的age 完成賦值
(age->__forwarding->age) = 20;
}
// 第五步
struct __test7_block_impl_0 {
struct __block_impl impl;
struct __test7_block_desc_0* Desc;
__Block_byref_objc_1 *objc; //__Block_byref_objc_1 類型結果體
__Block_byref_age_0 *age; //__Block_byref_age_0類型結果體
__test7_block_impl_0(void *fp, struct __test7_block_desc_0 *desc, __Block_byref_objc_1 *_objc, __Block_byref_age_0 *_age, int flags=0) : objc(_objc->__forwarding), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 看得眼花较坛,那我們走一個印蔗,其實前面搞好了,這里就一目了然:
1.第一步一樣丑勤,第二步多了兩個東西华嘹,其實是一個鬼東西,差不多 __Block_byref_age_0和__Block_byref_objc_1兩個結構體
2.第三就是對這兩個結構體的解析法竞,是不是就是一個類耙厚,包裝成類,然后再保存在block結構體里面
3.第四步就是賦值個過程了__forwarding指針為啥要這樣搞岔霸,明明已經(jīng)拿到*objc的指針了薛躬,又(objc->__forwarding->objc) 這樣拿objc,這他么不是嚇搞嗎?我們想一下呆细,前面說的內(nèi)存管理型宝,很多時候block會copy到堆上的,然后蘋果是這樣設計的 如圖:
圖片.png
圖片.png
這樣搞的好處就是:
- block在棧上的時候 block在棧上的 __forwarding指針指向自己
- block在堆上的時候 block在棧上的 __forwarding指針指向堆內(nèi)存 block在堆上的_forwarding指針指向堆內(nèi)存
- 所以你是指向棧還是堆 最后都能找到這個變量
二、__weak 修飾趴酣、 __strong修飾梨树、block的循環(huán)引用問題
- 其實到上面block已經(jīng)搞完了,這個步驟都是根據(jù)之前的解析的都可以推導出來岖寞,還是要自己去多推導 還是說說吧
- __weak 修飾的時候可以解除block的循環(huán)引用問題抡四,首先你要明白怎么樣才會造成循環(huán)引用問題:
- 兩個是否相互強引用 2.兩個以上是否形成強引用閉環(huán)
-
那么我們可以得出結論 block只有在堆上才會形成強引用,就是執(zhí)行copy操作的時候 對就是結合上一篇文章說道的內(nèi)存管理總結:
圖片.png
圖片.png 有些同學就問了 GCD里面的block調(diào)用self會嗎 UIview animation的block調(diào)用self會嗎 self要用__weak 修飾嗎仗谆?嗯 是不會的 因為 GCD 跟你的UIView 沒有強引用block 沒有形成相互強引用床嫌,還有很多第三方庫的block里面用self 也是一樣的,首先你要判斷的是 self有沒有強引用第三方的block 比如: Masonry AFN 第三方的block跟你的類不是強引用關系胸私,所以不會形成循環(huán)引用
有同學會問 NSTimer的block里面會是強引用呢厌处? 因為timer是self的屬性strong的啊,然后你在人家的block里面調(diào)用肯定就形成相互強引用
最后關于循環(huán)引用的問題就是前面說的兩個
- 兩個是否相互強引用 2.兩個以上是否形成強引用閉環(huán)
還有種特殊的場景比較少見岁疼,用__weak 修飾的對象阔涉,什么情況下會在block里面再用__strong修飾,就是在block里面執(zhí)行多線程耗時操作的時候捷绒,為啥這么說瑰排,因為__weak 修飾的對象block結束的時候block里面的對象就釋放了,可是你后面的線程還要使用block里面的對象所以就會有空指針的問題(weak/__weak 修飾的對象釋放后為nil)
關于block的底層解析就到這里了暖侨,也是對自己學習block的一個總結吧椭住,以后遇到block的難題基本都是這樣一套推倒,還有一些深入的東西和細節(jié)估計說得不到位字逗,希望各位大老爺指出