Block詳解

窺探block底層結(jié)構(gòu)

我們寫下一個(gè)最簡(jiǎn)單的block使用clang指令生成對(duì)應(yīng)的C\C++代碼

void (^block)(void) = ^{
    NSLog(@"Hello, World!");
};
block();

截取關(guān)鍵代碼如下

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)體對(duì)象
  __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) {
            //這一句就是打印"Hello, World!"
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}

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)};

// 定義block變量
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA
                                           );

// 執(zhí)行block內(nèi)部的代碼
block->FuncPtr(block);

從上面代碼可以看出厂财,block本質(zhì)上也是一個(gè)OC對(duì)象粹湃,內(nèi)部也有個(gè)isa指針他嚷,并且內(nèi)部封裝了函數(shù)調(diào)用墓赴。

block的變量捕獲

寫下一個(gè)訪問(wèn)外部變量的block

int age = 10;
void (^block)(void) = ^{
    NSLog(@"age is %d",age);
};
block();

生成C\C++代碼钉赁,截取關(guān)鍵部分

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
};
//block內(nèi)部的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_b9df52_mi_0,age);
}

int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

我們可以觀察到block結(jié)構(gòu)體多了個(gè)age變量,并且在初始化block時(shí)郭厌,將外部的age變量賦值給了結(jié)構(gòu)體內(nèi)部這個(gè)age變量袋倔,當(dāng)函數(shù)執(zhí)行時(shí),直接打印的是結(jié)構(gòu)體內(nèi)部的age變量沪曙。
所以我們可以總結(jié)一下奕污,block就是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
為了保證block內(nèi)部能正常訪問(wèn)外部的變量萎羔,block有個(gè)變量捕獲機(jī)制

變量類型 是否捕獲 訪問(wèn)方式
auto局部變量 值傳遞
static局部變量 指針傳遞
全局變量 直接訪問(wèn)

局部變量不寫修飾默認(rèn)就是auto變量液走,其實(shí)從內(nèi)存上也很好理解block的捕獲機(jī)制。auto局部變量在棧區(qū)贾陷,函數(shù)調(diào)用完后資源會(huì)被釋放掉缘眶,而static局部變量是在程序運(yùn)行過(guò)程中一直存在的(存在數(shù)據(jù)段),所以用指針隨時(shí)可以找到髓废,而全局變量本來(lái)就是在哪都可訪問(wèn)巷懈,根本沒(méi)必要捕獲。

block的類型

block有三種類型慌洪,可以通過(guò)class方法或者isa指針查看具體的類型顶燕,它們最終都繼承自NSBlock

類型 判斷依據(jù) 存儲(chǔ)區(qū)域 調(diào)用copy結(jié)果
__NSGlobalBlock__ 沒(méi)有訪問(wèn)auto變量 數(shù)據(jù)段 什么也不做
__NSStackBlock__ 訪問(wèn)了auto變量 棧區(qū) 從棧區(qū)復(fù)制到堆
__NSMallockBlock__ __NSStackBlock__調(diào)用了copy 堆區(qū) 引用計(jì)數(shù)增加

在ARC環(huán)境下會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況

  • block作為函數(shù)返回值時(shí)
  • block賦值給__strong指針時(shí)(對(duì)象類型的默認(rèn)修飾就是__strong)
  • block作為Cocoa API中方法名含有usingBlock參數(shù)時(shí)
  • block作為GCD API的方法參數(shù)時(shí)
    MRC下建議用copy修飾block屬性冈爹,ARC可以用strong和copy修飾block屬性
block訪問(wèn)對(duì)象類型的auto變量

寫下如下代碼涌攻,用clang指令生成C\C++

NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
    NSLog(@"%@",obj);
};
block();

截取部分關(guān)鍵代碼,可以看到
當(dāng)block訪問(wèn)對(duì)象類型的auto變量時(shí)频伤,內(nèi)部多了copy函數(shù)和dispose函數(shù)

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
//定義block代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
  • 當(dāng)block在棧上時(shí)恳谎,不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
  • 當(dāng)block從棧上被拷貝到堆上時(shí)
    會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)會(huì)調(diào)用內(nèi)部的_Block_object_assign函數(shù)憋肖,該函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong因痛、__weak、__unsafe_unretained)做出相應(yīng)的操作岸更,形成強(qiáng)引用或弱引用
  • 當(dāng)block從堆上移除時(shí)
    block會(huì)調(diào)用內(nèi)部的dispose函數(shù)鸵膏,dispose函數(shù)會(huì)調(diào)用內(nèi)部的_Block_object_dispose函數(shù),該函數(shù)會(huì)釋放引用的auto變量(release)
__block修飾符

__block可以用于解決block內(nèi)部無(wú)法修改auto變量問(wèn)題怎炊,__block不能用來(lái)修飾全局變量谭企,靜態(tài)變量(static)

__block int age = 10;
^{
    age = 30;
}();
NSLog(@"age is %d",age);

生成C\C++代碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
}
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
//__block int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344))();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_ad80c7_mi_0,(age.__forwarding->age));

當(dāng)我們使用__block修飾age變量時(shí)用僧,會(huì)將age變量包轉(zhuǎn)成age對(duì)象,age對(duì)象里的int age存儲(chǔ)著最初的age值赞咙,__forwarding指針是指向age對(duì)象自己责循,block捕獲的是age對(duì)象的地址值。從最后的一句可以看出攀操,當(dāng)我們使用__block修飾auto變量后院仿,訪問(wèn)age都變成了訪問(wèn)age對(duì)象里的age成員變量。

  • __forwarding指針
    當(dāng)block從棧上拷貝到堆上時(shí)速和,棧上對(duì)象的__forwarding會(huì)指向堆上的拷貝對(duì)象(block拷貝到堆上時(shí)歹垫,會(huì)將捕獲的對(duì)象變量一并copy到堆上)
block循環(huán)引用問(wèn)題

從上面我們可以看到,block訪問(wèn)對(duì)象類型的auto變量時(shí)有可能會(huì)產(chǎn)生強(qiáng)引用颠放,當(dāng)訪問(wèn)的auto變量又對(duì)block產(chǎn)生強(qiáng)引用時(shí)就會(huì)發(fā)生循環(huán)應(yīng)用排惨。舉例如下

typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
//main函數(shù)里面
Person *person = [[Person alloc] init];
person.myBlock = ^{
    NSLog(@"%@",person);
};

在ARC環(huán)境下可以使用__weak、__unsafe_unretained解決(一般使用__weak碰凶,會(huì)自動(dòng)置nil)

Person *person = [[Person alloc] init];
//或者 __unsafe_unretained typeof(Person *) weakPerson = person;
__weak typeof(Person *) weakPerson = person;
person.myBlock = ^{
    NSLog(@"%@",weakPerson);
};

在MRC環(huán)境下可以使用__unsafe_unretained解決

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暮芭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子欲低,更是在濱河造成了極大的恐慌辕宏,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾莱,死亡現(xiàn)場(chǎng)離奇詭異瑞筐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)腊瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門聚假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人闰非,你說(shuō)我怎么就攤上這事膘格。” “怎么了河胎?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵闯袒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我游岳,道長(zhǎng)政敢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任胚迫,我火速辦了婚禮喷户,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘访锻。我一直安慰自己褪尝,他們只是感情好闹获,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著河哑,像睡著了一般避诽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上璃谨,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天沙庐,我揣著相機(jī)與錄音,去河邊找鬼佳吞。 笑死拱雏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的底扳。 我是一名探鬼主播铸抑,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衷模!你這毒婦竟也來(lái)了鹊汛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤算芯,失蹤者是張志新(化名)和其女友劉穎柒昏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熙揍,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年氏涩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了届囚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡是尖,死狀恐怖意系,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饺汹,我是刑警寧澤蛔添,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站兜辞,受9級(jí)特大地震影響迎瞧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逸吵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一凶硅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扫皱,春花似錦足绅、人聲如沸捷绑。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粹污。三九已至,卻和暖如春首量,著一層夾襖步出監(jiān)牢的瞬間厕怜,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工蕾总, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粥航,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓生百,卻偏偏與公主長(zhǎng)得像递雀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚀浆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 第一部分:Block本質(zhì) Q:什么是Block缀程,Block的本質(zhì)是什么? block本質(zhì)上也是一個(gè)OC對(duì)象市俊,它內(nèi)部...
    sheldon_龍閱讀 554評(píng)論 0 0
  • Hash杨凑,一般翻譯做”散列“,也有直接音譯為”哈习诿粒“的撩满,就是把任意長(zhǎng)度的輸入通過(guò)散列算法變換成固定長(zhǎng)度的輸出,該輸...
    非洲小白猿閱讀 1,402評(píng)論 0 4
  • 一绅你、Block本質(zhì) Block是“帶有自動(dòng)變量值的匿名函數(shù)”伺帘。 所謂的匿名函數(shù)就是不帶有名稱的函數(shù) 但它究竟是什么...
    楓葉情結(jié)閱讀 482評(píng)論 1 0
  • 開(kāi)始之前,我想先提幾個(gè)問(wèn)題忌锯,看看大家是否對(duì)此有疑惑伪嫁。唐巧已經(jīng)寫過(guò)一篇對(duì)block很有研究的文章,大家可以去看看(本...
    高思陽(yáng)閱讀 1,604評(píng)論 0 1
  • 轉(zhuǎn)自李峰峰博客 一偶垮、概述 閉包 = 一個(gè)函數(shù)「或指向函數(shù)的指針」+ 該函數(shù)執(zhí)行的外部的上下文變量「也就是自由變量」...
    Joshua520閱讀 965評(píng)論 0 0