iOS 底層 day08 block 底層結(jié)構(gòu) 變量捕獲 類型

一毯盈、block 引用外部變量時,對外部變量的捕獲(capture)情況

1. 引用全局變量的block病袄,能簡單說下ta的底層結(jié)構(gòu)嗎搂赋?
#import <Foundation/Foundation.h>
static int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^lspBlock)(void) = ^{
            NSLog(@"我是簡單的block:%d",age);
        };
        lspBlock();
    }
    return 0;
}
  • 使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 將代碼轉(zhuǎn)換成 C++代碼,有助于我們觀察代碼的本質(zhì)
  • 轉(zhuǎn)換獲得如下代碼
block中引用了全局變量
  • 從上圖益缠,我們可以獲得一些信息:
  • ① block內(nèi)部有 isa 指針脑奠,說明 Block 是一個 OC類
  • ② block 類中存儲著需要運行的函數(shù)指針FuncPtr ,以及對 block的描述信息 __main_block_desc_0
  • __mian_block_impl_0 結(jié)構(gòu)體中沒有 age 的信息幅慌,說明block沒有對全局變量 age 進行捕獲
2. 引用局部靜態(tài)變量的block捺信,能簡單說下ta的底層結(jié)構(gòu)嗎?
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int age = 10;
        void (^lspBlock)(void) = ^{
            NSLog(@"我是簡單的block:%d",age);
        };
        lspBlock();
    }
    return 0;
}

  • 轉(zhuǎn)換獲得如下代碼
引用局部變量的block
  • 從轉(zhuǎn)換后的代碼欠痴,我們可以清晰的看到迄靠,對于靜態(tài)局部變量,1block會采取捕獲指針的方式喇辽,拿到&age的地址值掌挚,賦值給__main_block_impl_0` 結(jié)構(gòu)體對象
3. 引用局部變量的block,能簡單說下ta的底層結(jié)構(gòu)嗎菩咨?
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^lspBlock)(void) = ^{
            NSLog(@"我是簡單的block:%d",age);
        };
        lspBlock();
    }
    return 0;
}
  • 轉(zhuǎn)換獲得如下代碼
引用局部變量的block
  • 我們可以發(fā)現(xiàn)吠式,__main_block_impl_0 中傳遞的是 age 這個值,說明block捕獲局部變量時抽米,是值捕獲特占。
4. 接下來我們總結(jié)一下 block引用外部變量,在各種情況下的捕獲情況
捕獲機制總結(jié)-牢記
5. 明白了 block 捕獲原理后云茸,我們來強化一下是目,請問下面代碼的輸出是多少?
#import <Foundation/Foundation.h>
int globalAge = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 20;
        static int staticAge = 30;
        void (^lspBlock1)(void) = ^{
            NSLog(@"我是簡單的block:%d",globalAge);
        };
        void (^lspBlock2)(void) = ^{
            NSLog(@"我是簡單的block:%d",age);
        };
        void (^lspBlock3)(void) = ^{
            NSLog(@"我是簡單的block:%d",staticAge);
        };
        
        globalAge++;
        age++;
        staticAge++;
        
        lspBlock1();
        lspBlock2();
        lspBlock3();
    }
    return 0;
}
  • 輸出如下标捺,你答對了嗎懊纳?
Demo[6041:138667] 我是簡單的block:11
Demo[6041:138667] 我是簡單的block:20
Demo[6041:138667] 我是簡單的block:31
6.請問下面的 block 有捕獲變量嗎揉抵?捕獲的是誰?
#import "Person.h"
@interface Person ()
@property(nonatomic, assign) int age;
@end

@implementation Person
- (void)lspTest {
    void (^block)(void) = ^{
        NSLog(@"%i", _age);
    };
    block();
}
@end
  • _age 實際上是 self->age嗤疯,而 self 是一個局部實例變量冤今,對于局部變量,block 會采取值捕獲 self
  • 還有一個疑惑茂缚,思考為什么我們在類中調(diào)用方法戏罢,可以直接使用 self_cmd 這兩個變量
  • 這是因為OC底層替我們在方法中傳遞了這兩個參數(shù),比如上述代碼轉(zhuǎn)換后
static void _I_Person_lspTest(Person * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__Person__lspTest_block_impl_0((void *)__Person__lspTest_block_func_0, &__Person__lspTest_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

二脚囊、block 的類型

1. 除了 block 有 isa 指針這個方面來說龟糕,我們還可以從哪方面證明 block 是一個 OC 對象?
  • 我們還可以在MRC下通過打印 block 的 class 和 superclass來說明凑术;
#import <Foundation/Foundation.h>
int globalAge = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 20;
        static int staticAge = 30;
        void (^lspBlock1)(void) = ^{
            NSLog(@"我是簡單的block:%d",globalAge);
        };
        void (^lspBlock2)(void) = ^{
            NSLog(@"我是簡單的block:%d",age);
        };
        void (^lspBlock3)(void) = ^{
            NSLog(@"我是簡單的block:%d",staticAge);
        };
        
        void (^lspBlock4)(void) = [lspBlock2 copy];
        
        NSLog(@"lspBlock1: %@, %@, %@, %@",[lspBlock1 class],
              [lspBlock1 superclass],
              [[lspBlock1 superclass] superclass],
              [[[lspBlock1 superclass] superclass] superclass]);
        
        
        NSLog(@"lspBlock2: %@, %@, %@, %@",[lspBlock2 class],
              [lspBlock2 superclass],
              [[lspBlock2 superclass] superclass],
              [[[lspBlock2 superclass] superclass] superclass]);
        
        
        NSLog(@"lspBlock3: %@, %@, %@, %@",[lspBlock3 class],
              [lspBlock3 superclass],
              [[lspBlock3 superclass] superclass],
              [[[lspBlock3 superclass] superclass] superclass]);
        
        NSLog(@"lspBlock4: %@, %@, %@, %@",[lspBlock4 class],
              [lspBlock4 superclass],
              [[lspBlock4 superclass] superclass],
              [[[lspBlock4 superclass] superclass] superclass]);
        
    }
    return 0;
}

  • 思考上述代碼的輸出:
Demo[6201:154701] lspBlock1: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock2: __NSStackBlock__, __NSStackBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock3: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock4: __NSMallocBlock__, __NSMallocBlock, NSBlock, NSObject
  • 我們發(fā)現(xiàn)block最終繼承于 NSObject ,再次證明 block 底層就是OC對象
  • 我們還發(fā)現(xiàn)所意,我們打印的block中主要有三種類型 __NSGlobalBlock 淮逊、__NSStackBlock__NSMallocBlock
  • 還發(fā)現(xiàn)扶踊,無論哪種類型的block都繼承于 NSBlock
2. 既然 block 有三種類型泄鹏,你能從它們的名字,區(qū)分出運行時秧耗,它們分別放在內(nèi)存的哪一段嗎备籽?
  • __NSGlobalBlock 放在數(shù)據(jù)區(qū),特點是:由系統(tǒng)管理分井,運行過程不會釋放
  • __NSStackBlock 放在棧區(qū)车猬,特點是:系統(tǒng)自動分配內(nèi)存,也會自動釋放內(nèi)存
  • __NSMallocBlock放在堆區(qū)尺锚,特點是:需要程序員自己管理內(nèi)存
3. 既然 block 有三種類型珠闰,那對它們進行 copy 操作會有什么效果呢?
copy 操作-牢記
4. 你可能會遇到的疑惑瘫辩,如果將 1 中的代碼伏嗜,轉(zhuǎn)換成 c++ 代碼
  • 你會獲得如下代碼
struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int age;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 我們重點注意這句話 impl.isa = &_NSConcreteStackBlock; ,我們發(fā)現(xiàn)無論哪種 block伐厌,C++代碼的都會是這句代碼
  • 這句話的含義就是將 _NSConcreteStackBlock 這個類對象賦值給 isa 指針
  • 也就說明承绸,從C++代碼層面,這些block都是 _NSConcreteStackBlock 挣轨,怎么回事呢军熏?
  • 當(dāng)我們遇到這種情況,一切以運行時的情況為準卷扮,因為運行時的結(jié)果羞迷,才是我們真正的最終結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末界轩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衔瓮,更是在濱河造成了極大的恐慌浊猾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件热鞍,死亡現(xiàn)場離奇詭異葫慎,居然都是意外死亡,警方通過查閱死者的電腦和手機薇宠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門偷办,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人澄港,你說我怎么就攤上這事椒涯。” “怎么了回梧?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵废岂,是天一觀的道長。 經(jīng)常有香客問我狱意,道長湖苞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任详囤,我火速辦了婚禮财骨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藏姐。我一直安慰自己隆箩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布羔杨。 她就那樣靜靜地躺著摘仅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪问畅。 梳的紋絲不亂的頭發(fā)上娃属,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音护姆,去河邊找鬼矾端。 笑死,一個胖子當(dāng)著我的面吹牛卵皂,可吹牛的內(nèi)容都是我干的秩铆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼殴玛!你這毒婦竟也來了捅膘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤滚粟,失蹤者是張志新(化名)和其女友劉穎寻仗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凡壤,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡署尤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了亚侠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曹体。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖硝烂,靈堂內(nèi)的尸體忽然破棺而出箕别,到底是詐尸還是另有隱情,我是刑警寧澤滞谢,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布串稀,位于F島的核電站,受9級特大地震影響爹凹,放射性物質(zhì)發(fā)生泄漏厨诸。R本人自食惡果不足惜镶殷,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一禾酱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眨八,春花似錦离钝、人聲如沸娃圆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帽馋,卻和暖如春搅方,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绽族。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工姨涡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吧慢。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓涛漂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匈仗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354