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è)訪問外部變量的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)部能正常訪問外部的變量,block有個(gè)變量捕獲機(jī)制

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

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

block的類型

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

類型 判斷依據(jù) 存儲(chǔ)區(qū)域 調(diào)用copy結(jié)果
__NSGlobalBlock__ 沒有訪問auto變量 數(shù)據(jù)段 什么也不做
__NSStackBlock__ 訪問了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訪問對(duì)象類型的auto變量

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

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

截取部分關(guān)鍵代碼,可以看到
當(dāng)block訪問對(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變量問題,__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變量后,訪問age都變成了訪問age對(duì)象里的age成員變量。

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

從上面我們可以看到,block訪問對(duì)象類型的auto變量時(shí)有可能會(huì)產(chǎn)生強(qiáng)引用,當(dāng)訪問的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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容