Block底層原理

底層數(shù)據(jù)結(jié)構(gòu)

創(chuàng)建一個命令行項目幅虑,在main函數(shù)中定義一個Block并進行調(diào)用

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"Hello World !");
        };
        block();
    }
    return 0;
}

在控制臺中輸入:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 將OC代碼轉(zhuǎn)為C++代碼

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    //構(gòu)造函數(shù)(類似OC的init方法)。返回結(jié)構(gòu)體對象
  __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;
  }
};
//封裝了block執(zhí)行邏輯的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_0a05f8_mi_0);
        }

//block的描述
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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        //定義block變量
//        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        //將一些強制轉(zhuǎn)換代碼刪除
        void (*block)(void) = &__main_block_impl_0(
                                                    __main_block_func_0,
                                                    &__main_block_desc_0_DATA
                                                   );
        //執(zhí)行block內(nèi)部的代碼
//        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        //將一些強制轉(zhuǎn)換代碼刪除
        block->FuncPtr(block);
    }
    return 0;
}

變量捕獲

1.基本數(shù)據(jù)類型auto變量
auto:自動變量顾犹,離開作用域就會銷毀

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int  age = 10;
        void (^block)(void) = ^{
            NSLog(@"Hello World ! %d",age);
        };
        age = 20;
        block();
    }
    return 0;
}

代碼運行打印的10翘单,下面我們就來分析一下底層代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
//age(_age)    C++ 語法,將傳進來的_age賦值給結(jié)構(gòu)體的成員變量age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_5736dc_mi_0,age);
        }
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
//定義block時將捕獲的age的值傳給main_block_impl_0這個函數(shù)蹦渣。
        void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
        age = 20;
        block)->FuncPtr(block);
    }
    return 0;
}

2.對象類型的auto變量

#import <Foundation/Foundation.h>
#import "Cat.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //沒有捕獲auto變量的block是Global
        Cat *cat = [[Cat alloc]init];
        cat.name = @"aaa";
        void (^block)(void) = ^{
            NSLog(@"Hello World ! %@",cat.name);
        };
        block();
        NSLog(@"%@",[block class]);
    }
    return 0;
}

在Cat類中重寫dealloc方法哄芜,打印Cat的釋放時間,運行上面代碼發(fā)現(xiàn)柬唯,Cat類不會被釋放认臊。
因為block會捕獲Cat類,強引用Cat類且block存放在堆上锄奢。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Cat *cat;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Cat *_cat, int flags=0) : cat(_cat) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Cat *cat = __cself->cat; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_b96010_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)cat, sel_registerName("name")));
        }
//在ARC模式下失晴,系統(tǒng)會自動調(diào)用copy方法,_Block_object_assign會根據(jù)捕獲的auto變量的修飾符(__strong 拘央、__weak)做出強引用或弱引用涂屁。 相當(dāng)于retain
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->cat, (void*)src->cat, 3/*BLOCK_FIELD_IS_OBJECT*/);}

//_Block_object_dispose會釋放捕獲的auto變量,相當(dāng)于release
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->cat, 3/*BLOCK_FIELD_IS_OBJECT*/);}

//當(dāng)block捕獲對象類型的auto變量灰伟,block底層的描述函數(shù)會多2個函數(shù)方法:copy  dispose 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Cat *cat = ((Cat *(*)(id, SEL))(void *)objc_msgSend)((id)((Cat *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)cat, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_b96010_mi_0);
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, cat, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_b96010_mi_2,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    }
    return 0;
}

3.static變量

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       static int  wide = 10;
        void (^block)(void) = ^{
            NSLog(@"Hello World ! %d",wide);
        };
        wide = 20;
        block();
    }
    return 0;
}

打印的值是20

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *wide;
//wide(_wide)    C++ 語法拆又,將傳進來的__wide賦值給結(jié)構(gòu)體的成員變量*wide
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_wide, int flags=0) : wide(_wide) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *wide = __cself->wide; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_d13ed3_mi_0,(*wide));
        }

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
       static int wide = 10;
        //定義block時將捕獲的wide的地址傳給main_block_impl_0這個函數(shù)儒旬。
        void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &wide);
        wide = 20;
        block)->FuncPtr(block);
    }
    return 0;
}

4.全局變量
block不會捕獲全局變量,在block可以直接使用帖族。
self是局部變量栈源,block會捕獲self,每個OC方法都有2個隱藏參數(shù)竖般,self作為第一個參數(shù)

block類型

1.NSGlobalBlock

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //沒有捕獲auto變量的block是Global
        static int  age = 10;
        void (^block)(void) = ^{
            NSLog(@"Hello World ! %d",age);
        }; 
        NSLog(@"%@",[block class]);
    }
    return 0;
}
打印為:2021-09-17 16:50:01.955120+0800 Block的本質(zhì)[60317:3405819] __NSGlobalBlock__

2.NSStackBlock
關(guān)閉Xcode的ARC甚垦,將Objective-C Automatic Reference Counting 的值設(shè)為NO,NSStackBlock存儲在棧上涣雕,當(dāng)block出了作用域艰亮,NSStackBlock就會被回收,再次調(diào)用時會出現(xiàn)數(shù)據(jù)錯亂挣郭。

截屏2021-09-17 下午4.57.04.png
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //捕獲auto變量的block是Stack
        int  age = 10;
        void (^block)(void) = ^{
            NSLog(@"Hello World ! %d",age);
        }; 
        NSLog(@"%@",[block class]);
    }
    return 0;
}
打印為:2021-09-17 16:55:35.195563+0800 Block的本質(zhì)[60355:3408920] __NSStackBlock__

3.NSMallocBlock
當(dāng)NSStackBlock調(diào)用copy方法后迄埃,就會變?yōu)镹SMallocBlock,存儲在堆上丈屹。
當(dāng)NSGlobalBlock調(diào)用copy方法不會改變block類型调俘,還是存儲在數(shù)據(jù)區(qū)。
當(dāng)NSMallocBlock調(diào)用copy方法旺垒,block對象的引用計數(shù)器會加一彩库。

在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上:
block作為函數(shù)返回值時
將block賦值給__strong指針時
block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
block作為GCD API中方法參數(shù)時

block修改變量的值

1.將auto局部變量加上static 修飾符 缺點:變量會一直存儲在內(nèi)存中先蒋,不會在超出作用域時釋放骇钦。
2.__block修飾 編譯器會將變量包裝成一個對象

//系統(tǒng)會將age 包裝給一個對象
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_255acd_mi_0,(age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 



        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_h2psj2ss52s_y619pxn42rfh0000gn_T_main_255acd_mi_1,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市竞漾,隨后出現(xiàn)的幾起案子眯搭,更是在濱河造成了極大的恐慌,老刑警劉巖业岁,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳞仙,死亡現(xiàn)場離奇詭異,居然都是意外死亡笔时,警方通過查閱死者的電腦和手機棍好,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允耿,“玉大人借笙,你說我怎么就攤上這事〗衔” “怎么了业稼?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚂蕴。 經(jīng)常有香客問我低散,道長俯邓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任谦纱,我火速辦了婚禮看成,結(jié)果婚禮上君编,老公的妹妹穿的比我還像新娘跨嘉。我一直安慰自己,他們只是感情好吃嘿,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布祠乃。 她就那樣靜靜地躺著,像睡著了一般兑燥。 火紅的嫁衣襯著肌膚如雪亮瓷。 梳的紋絲不亂的頭發(fā)上兆览,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天铐炫,我揣著相機與錄音锁右,去河邊找鬼冗尤。 笑死仪搔,一個胖子當(dāng)著我的面吹牛锚赤,可吹牛的內(nèi)容都是我干的凝化。 我是一名探鬼主播扁远,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼扔枫,長吁一口氣:“原來是場噩夢啊……” “哼汛聚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起短荐,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤倚舀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忍宋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痕貌,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年糠排,在試婚紗的時候發(fā)現(xiàn)自己被綠了舵稠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡乳讥,死狀恐怖柱查,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情云石,我是刑警寧澤唉工,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站汹忠,受9級特大地震影響淋硝,放射性物質(zhì)發(fā)生泄漏雹熬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一谣膳、第九天 我趴在偏房一處隱蔽的房頂上張望竿报。 院中可真熱鬧,春花似錦继谚、人聲如沸烈菌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芽世。三九已至,卻和暖如春诡壁,著一層夾襖步出監(jiān)牢的瞬間济瓢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工妹卿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旺矾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓夺克,卻偏偏與公主長得像箕宙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子懊直,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容