Block 探究

Block是OSX Snow Leopard和iOS 4引入的C語言擴(kuò)充功能,是一個(gè)帶有自動(dòng)變量(局部變量)的匿名函數(shù),也被稱為閉包。

Block語法

Block的表達(dá)式語法:
^ 返回值類型 參數(shù)列表 表達(dá)式
^ int (int count) { return count + 1; }

// 可省略返回值類型,如果表達(dá)式中有返回值就使用返回值的類型
^(int count) { return count + 1; }

// 如果沒有參數(shù),參數(shù)列表也可以省略
^{ printf("hello Blocks\n"); }

Block 類型變量

Block類型變量與一般的C語言變量完全相同,可作為:自動(dòng)變量、函數(shù)參數(shù)、靜態(tài)變量、靜態(tài)全局變量、全局變量。

聲明Block類型變量的方式

  • 使用Block語法將Block賦值為Block類型變量
返回值 (^變量名)(參數(shù)列表) = ^參數(shù)列表 表達(dá)式
int (^block)(int) = ^(int count) { return count + 1; }
  • 使用typedef來聲明Block類型變量
// 定義Block類型
typedef 返回值 (^變量名)(參數(shù)列表)
typedef int (^block_t)(int count);
// 聲明block_t類型的Block變量
block_t block;

Block的實(shí)現(xiàn)與本質(zhì)

Blcok語法實(shí)際上是作為極普通的C語言源代碼來處理的,可以在shell中通過clang命令將OC文件轉(zhuǎn)換為源碼文件。

clang -rewrite-objc 文件名

將以下Block語法轉(zhuǎn)換為源代碼形式

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            printf("hello block!");
        };
        
        block();
    }
    return 0;
}

源碼為:

// Block的結(jié)構(gòu)體實(shí)例的信息
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// Block的結(jié)構(gòu)體實(shí)例
struct __main_block_impl_0 {
  // 結(jié)構(gòu)體實(shí)例成員變量信息:結(jié)構(gòu)體實(shí)例類型、函數(shù)等
  struct __block_impl impl;
  // 結(jié)構(gòu)體實(shí)例信息:區(qū)域和大小
  struct __main_block_desc_0* Desc;
  
  // __main_block_impl_0結(jié)構(gòu)體的構(gòu)造方法
  // 第一個(gè)參數(shù) __main_block_func_0 是block變量的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)指針
  // 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    // 實(shí)例類型名
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    // block的表達(dá)式函數(shù)
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/*
 * __main_block_func_0函數(shù) 是的Block實(shí)例的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)
 * __cself是指向Blcok對(duì)象自身的變量,就像Objective-C中的self
**/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("hello block!");
}

// 其描述的是 __main_block_impl_0結(jié)構(gòu)體實(shí)例的區(qū)域和大小
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)};

// main.m文件
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

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

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

跟著代碼執(zhí)行流程走,首先執(zhí)行

void (^block)(void) = ^{
    printf("hello block!");
};

下面源代碼將在棧上__main_block_impl_0 結(jié)構(gòu)體實(shí)例的指針賦值給 __main_block_impl_0 結(jié)構(gòu)體指針類型的變量block。

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

// 去掉類型轉(zhuǎn)換
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *block = &tmp

__main_block_impl_0 構(gòu)造函數(shù)的第一個(gè)參數(shù) __main_block_func_0 是block變量的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)指針,第二個(gè)參數(shù) __main_block_desc_0_DATA中存儲(chǔ)著 __main_block_impl_0 結(jié)構(gòu)體實(shí)例大小。

聲明好了block變量,就到了下一步,使用block:block()

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

// 去掉類型轉(zhuǎn)換
(*block->FuncPtr)(block);

此步驟就是簡單的使用函數(shù)指針調(diào)用函數(shù),所調(diào)用的函數(shù)就是上面源碼的 __main_block_func_0 函數(shù)。

總結(jié):由上可知Block實(shí)質(zhì)就是Objective-C的對(duì)象。

Block捕獲自動(dòng)變量

int main () {
    int val = 10;
    void (^block)(void) = ^{
        printf("val = %d", val);
    };
    
    val = 1;
    
    block();
}

上面的示例的結(jié)果是:val = 10,由于Block捕獲的是val的值,所以外部val變量的改動(dòng)并不會(huì)影響到Block中的val變量,但是,如果我們?cè)噲D在Block中改變val的值,由于Blcok捕獲的是val變量的值而不是地址,所以在Block中無法修改自動(dòng)變量val的值,因此編譯器會(huì)報(bào)錯(cuò),這一點(diǎn)在下節(jié)的“Block 是如何捕獲自動(dòng)變量”可明白。

int val = 10;
void (^block)(void) = ^{ val = 1; };

如果我們只使用值的話就不會(huì)有任何問題

int val = 10;
void (^block)(void) = ^{ printf("val = %d", val); };

id multArray = [NSMutableArray alloc] init];
void (^block)(void) = ^{ 
    [multArray addObject:@"use is ok"];
}

當(dāng)然也有特殊情況,當(dāng)我們?cè)贐lock中只使用C語言的字符串字面變量數(shù)組時(shí),由于Block捕獲自動(dòng)變量的方法沒有實(shí)現(xiàn)對(duì)C語言數(shù)組的截獲,所以在Block中使用C語言的字符串字面變量數(shù)組會(huì)出錯(cuò)。

const char text[] = "hello";
void (^block)(void) = ^{
    printf("%c\n",text[2]);
}

// 該用此做法就能解決問題
const char text = "hello";
void (^block)(void) = ^{
    printf("%c\n",text[2]);
}

Block 是如何捕獲自動(dòng)變量

將捕獲自動(dòng)變量的代碼轉(zhuǎn)換為源代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int val = 10;
        void (^block)(void) = ^{
            printf("val = %d", val);
        };
        
        block();
    }
    return 0;
}

源碼:

struct __main_block_impl_0 {

  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    int val = __cself->val; // bound by copy

    printf("val = %d", val);
        
}

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; 

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

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

從上面的代碼可知道,Block將在表達(dá)式中用到的val自動(dòng)變量作為成員變量追加到 __main_block_impl_0 結(jié)構(gòu)體中

struct __main_block_impl_0 {

  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
};

跟著程序執(zhí)行流程

int val = 10;
void (^block)(void) = ^{
    printf("val = %d", val);
};

// 源碼:
int val = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));

定義 val變量并賦予初始值10,將val值通過 __main_block_impl_0 的構(gòu)造方法對(duì)__main_block_impl_0追加的val成員變量進(jìn)行初始化。

// __main_block_impl_0 初始化
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = __main_block_desc_0_DATA;
val = 10;

使用block:block(),也就是執(zhí)行

^{ printf("val = %d", val); }

源碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    int val = __cself->val; // bound by copy

    printf("val = %d", val);
        
}

在執(zhí)行block的表達(dá)式時(shí),用的val自動(dòng)變量,是結(jié)構(gòu)體追加的val自動(dòng)變量,所以Block捕獲自動(dòng)變量并不是真的將變量直接作為成員變量,而是在結(jié)構(gòu)體中追加一個(gè)名字相同的成員變量,在構(gòu)造方法中將要捕獲的自動(dòng)變量值對(duì)追加的成員變量初始化。

__block 說明符

在此之前試圖在block表達(dá)式中改變捕獲的自動(dòng)變量的值時(shí)編譯器會(huì)報(bào)錯(cuò),解決方案有:

  • 用__block 說明符修飾自動(dòng)變量
  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int static_val = 3;
        __block int block_val = 4;
        void (^block)(void) = ^{
            
            global_val = 2;
            static_global_val = 3;
            static_val = 4;
            block_val = 5;
        };
        
        block();
        
        printf("global_val: %d \n", global_val);
        printf("static_global_val: %d \n", static_global_val);
        printf("static_val: %d \n", static_val);
        printf("block_val: %d \n", block_val);
    }
    return 0;
}

// 結(jié)果:
global_val: 2 
static_global_val: 3 
static_val: 4 
block_val: 5 

轉(zhuǎn)換的源碼為:

int global_val = 1;
static int static_global_val = 2;
struct __Block_byref_block_val_0 {
  void *__isa;
__Block_byref_block_val_0 *__forwarding;
 int __flags;
 int __size;
 int block_val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __Block_byref_block_val_0 *block_val; // by ref
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, __Block_byref_block_val_0 *_block_val, int flags=0) : static_val(_static_val), block_val(_block_val->__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_block_val_0 *block_val = __cself->block_val; // bound by ref
  int *static_val = __cself->static_val; // bound by copy
  
  global_val = 2;
  static_global_val = 3;
  (*static_val) = 4;
  (block_val->__forwarding->block_val) = 5;
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_val, (void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_val, 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; 

        static int static_val = 3;
        __attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,(__Block_byref_block_val_0 *)&block_val, 0, sizeof(__Block_byref_block_val_0), 4};
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val, (__Block_byref_block_val_0 *)&block_val, 570425344));

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

        printf("global_val: %d \n", global_val);
        printf("static_global_val: %d \n", static_global_val);
        printf("static_val: %d \n", static_val);
        printf("block_val: %d \n", (block_val.__forwarding->block_val));
    }
    return 0;
}

以下是對(duì)四種變量改變值的表達(dá)式函數(shù)

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  __Block_byref_block_val_0 *block_val = __cself->block_val; // bound by ref
  int *static_val = __cself->static_val; // bound by copy
  
  global_val = 2;
  static_global_val = 3;
  (*static_val) = 4;
  (block_val->__forwarding->block_val) = 5;
}

Block僅捕獲了 block_val修飾的變量和staic_val,全局變量沒有捕獲,原因是block_val、staic_val作為局部變量,其作用域僅在 main 方法中,當(dāng)離開作用域時(shí)變量會(huì)被廢棄,所以Block需捕獲block_val、staic_val變量以保證在表達(dá)式函數(shù)的使用,然而全局變量的作用域很廣,所以Block無需捕獲,所以看出Block只對(duì)需要捕獲的變量進(jìn)行捕獲。

對(duì)于全局變量能在Block中修改值,如上面說的,它作用域很廣,所以在Block表達(dá)式函數(shù)結(jié)束后也能保存修改的值。靜態(tài)變量static_val是將其指針傳遞給 __main_block_impl_0 結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,也就是說Block捕獲的并不是 static_val的值,而是其指針(即內(nèi)存地址),所以static_val能夠在超出作用域之外使用,在Block結(jié)束后也能保存修改后的值。

最后就是block_val變量了,block_val 是使用__block 說明符修飾的變量,“__block 說明符”也被稱為“__block 存儲(chǔ)域類說明符”,block_val在添加上“__block 說明符”后,源碼變換如下

__block int block_val = 4;

// 轉(zhuǎn)換后的源碼:
__attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,(__Block_byref_block_val_0 *)&block_val, 0, sizeof(__Block_byref_block_val_0), 4};
//去掉類型轉(zhuǎn)換后:
__Block_byref_block_val_0 block_val = { 0, &block_val, 0, sizeof(__Block_byref_block_val_0), 4};

// __Block_byref_block_val_0 結(jié)構(gòu)體
struct __Block_byref_block_val_0 {
  void *__isa;
  __Block_byref_block_val_0 *__forwarding;
  int __flags;
  int __size;
  int block_val;
};

在添加上“__block 說明符”后,block_val變成了 __Block_byref_block_val_0 結(jié)構(gòu)體類型的自動(dòng)變量,并且其結(jié)構(gòu)體中含有一個(gè)相當(dāng)于原自動(dòng)變量block_val的成員變量,當(dāng)對(duì)block_val變量賦值時(shí)

__Block_byref_block_val_0 *block_val = __cself->block_val; 

(block_val->__forwarding->block_val) = 5;

Blcok的 __main_block_impl_0 結(jié)構(gòu)體實(shí)例持有指向 block_val變量的__Block_byref_block_val_0 結(jié)構(gòu)體實(shí)例的指針,而__Block_byref_block_val_0 結(jié)構(gòu)體實(shí)例的成員變量 __forwarding 持有永遠(yuǎn)指向自身的指針,可以通過 __forwarding來訪問成員變量 block_val,因此在Block結(jié)束后,block_val變量能保存改動(dòng)后的值。這里還有一個(gè)疑問:block_val超出了block變量的作用域并且它是配置在棧上,為什么還能訪問?這個(gè)問題在下節(jié)“Block變量和__block變量存儲(chǔ)域”可以明白。

總結(jié):要在Blcok中改變被捕獲的自動(dòng)變量的值的方式有:

  • 以指針(內(nèi)存地址)形式被Block捕獲,Block保存其指針后,對(duì)于內(nèi)容的更改便不會(huì)丟失
  • 改變自動(dòng)變量的存儲(chǔ)方式,如__block, __Block_byref_block_val_0結(jié)構(gòu)體實(shí)例中擁有相當(dāng)于原自動(dòng)變量的成員變量并擁有永遠(yuǎn)指向自己的指針的成員變量__forwarding,不管變量在棧上還是堆上都能訪問。

Block變量和__block變量存儲(chǔ)域

上面所述可知,Block類型變量轉(zhuǎn)換為 __main_block_impl_0 結(jié)構(gòu)體類型變量,__block 變量轉(zhuǎn)換為 __Block_byref_block_val_0結(jié)構(gòu)體類型變量,所謂的結(jié)構(gòu)體類型變量即棧上生成的結(jié)構(gòu)體實(shí)例。

Block實(shí)質(zhì)就是Objective-C對(duì)象,它有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock。

  • _NSConcreteStackBlock:
    它是在棧上的Block類型,只用到外部局部變量、成員屬性變量,沒有強(qiáng)指針引用,一旦脫離作用域時(shí)就會(huì)被銷毀。
  • _NSConcreteGlobalBlock:
    它是在數(shù)據(jù)區(qū)域(.data區(qū))的Block類型,沒有用到外界變量或只用到全局變量的block,只有在應(yīng)用程序結(jié)束才會(huì)被銷毀。
  • _NSConcreteMallocBlock
    它是在堆(內(nèi)存塊)里的Block類型,有強(qiáng)指針引用或copy修飾的成員屬性引用,沒有強(qiáng)指針引用即銷毀。
image

_NSConcreteGlobalBlock 類型Block變量在超出作用域也能通過指針訪問,而_NSConcreteStackBlock類型Block在作用域結(jié)束后就會(huì)被廢棄,同樣的 __block 類型變量也是如此,為解決這個(gè)問題,Block語法提供了將Block 和 __block變量從棧上復(fù)制到堆上,這樣在Block變量作用域結(jié)束后,堆上的Block還能繼續(xù)存在。復(fù)制在堆上的Block會(huì)將 _NSConcreteMallocBlock 類名寫入Block 結(jié)構(gòu)體實(shí)例 __main_block_impl_0 中的成員變量 ipml 的isa變量中。

ipml.isa = &_NSConcreteMallocBlock;

而 __block 結(jié)構(gòu)體實(shí)例的成員變量 __forwarding 擁有永遠(yuǎn)指向自身的指針, 可以實(shí)現(xiàn)無論 __block 變量配置在棧上還是堆上也可以訪問 __block變量,因此即使__block變量在Block變量的作用域之外也能訪問__block變量并保存更改后的值。

Block 的copy方法和dispose方法

這是在MRC環(huán)境下的代碼示例

typedef void (^block_t)(id);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        id array = [[NSMutableArray alloc] init];
        block_t block = [^(id obj) {
           
            [array addObject:obj];
            NSLog(@"array count = %ld", [array count]);
        } copy];
        
        block([[NSObject alloc] init]);
        block([[NSObject alloc] init]);
        block([[NSObject alloc] init]);
    }
    return 0;
}

// clang后的源碼:
typedef void (*block_t)(id);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {

  id array = __cself->array; // bound by copy

  ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2k_lvtjwq3x1h746y288_mm11200000gn_T_main_580dfd_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        
}
        
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        
        block_t block = (block_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));

        ((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

    }
    return 0;
}

上面代碼中 Block捕獲了一個(gè) __strong 類型的變量 array,雖然源碼中沒有標(biāo)識(shí)。在Objective-C中,C語言結(jié)構(gòu)體不能含有 __strong 類型的變量,因?yàn)樵贐lock從棧上復(fù)制到堆以及堆上的Block廢棄時(shí),編譯器不知道何時(shí)進(jìn)行C語言結(jié)構(gòu)體的初始化和廢棄操作,但是由于 Objective-C的運(yùn)行庫能夠準(zhǔn)確把握Block從棧上復(fù)制到堆以及堆上的Block廢棄的時(shí)機(jī),因此在 __main_block_desc_0增加了 copy、dispose成員變量并賦予__main_block_copy_0、__main_block_dispose_0函數(shù),通過這個(gè)兩個(gè)函數(shù)管理此類變量的復(fù)制和廢棄。

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_FIELD_IS_OBJECT 和 BLOCK_FIELD_IS_BYREY 用于分辨copy和dispose函數(shù)的對(duì)象類型是對(duì)象還是 __block變量。
// 在Block 從棧上復(fù)制到堆上時(shí),會(huì)調(diào)用__main_block_copy_0 將 __strong 類型變量跟隨Block復(fù)制到堆上
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 當(dāng)堆上Block 廢棄時(shí),__main_block_dispose_0 將 __strong 類型變量廢棄
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

在ARC下,編譯器會(huì)適當(dāng)?shù)剡M(jìn)行判斷去自動(dòng)執(zhí)行copy,而在MRC下需要自己手動(dòng)copy,看下面在ARC環(huán)境下的一個(gè)返回Block的函數(shù):

typedef int (^blk_t)(int);
blk_t func(int rate) {
            
    return ^(int count) { return rate *count; };
};

ARC編譯器轉(zhuǎn)換簡約后:
blk_t func(int race) {

    blk_t tmp = &__func_block_impl_0(__func_block_func0, &__func_block_desc_0_DATA, rate);
    
    tmp = objc_retainBlock(tmp);
    
    return objc_autoreleaseReturnValue(tmp);
}

第一步,通過Block結(jié)構(gòu)體構(gòu)造函數(shù)生成配置在棧上的結(jié)構(gòu)體實(shí)例并將其賦給Block 變量 tmp。第二步,objc_retainBlock()函數(shù)實(shí)質(zhì)是_Block_copy函數(shù)(由objc4 運(yùn)行庫可知),將棧上的Block變量tmp復(fù)制到堆上。第三步,將堆上的Block變量tmp作為Objective-C對(duì)象注冊(cè)到自動(dòng)釋放池中,然后返回改對(duì)象。雖說ARC下,編譯器會(huì)適當(dāng)?shù)剡M(jìn)行判斷去自動(dòng)生成“將Block 變量從棧上復(fù)制到堆上”的代碼,但是在此之外的情況,我們需要自己手動(dòng)copy,比如 alloc/new/copy/mutableCopy等方法其實(shí)都是將對(duì)象從棧上復(fù)制到堆上的操作。在ARC中有以下情況需要手動(dòng)copy:

  • 向方法或者函數(shù)的參數(shù)中傳遞Block之前時(shí),當(dāng)然如果方法或或者函數(shù)中有對(duì)Block進(jìn)行復(fù)制的操作,那便不需要。
  • NSArray類的 initWithObjects 實(shí)例方法

在ARC中有以下情況不需要手動(dòng)copy:

  • Block作為函數(shù)值返回時(shí)
  • 將 Block賦值給 使用 __strong 修飾符的id類型或 Block類型變量
  • Coccoa 框架的方法且方法名中含有 usingBlock 中使用時(shí)。
  • Grand Central Dispatch 的API中

對(duì)于不同類型的Block進(jìn)行copy操作后的情況

Block 的類                   副本源的配置存儲(chǔ)域                 復(fù)制結(jié)果
_NSConcreteStackBlock        棧                            從棧復(fù)制到堆
_NSConcreteGlobalBlock       程序的數(shù)據(jù)區(qū)域                   什么也不做
_NSConcreteMallocBlock       堆                            引用計(jì)數(shù)增加

不管Block配置在何處,用copy方法都不會(huì)引起任何問題,因此在不確定的時(shí)候可以調(diào)用copy方法,但是需注意從棧上復(fù)制到堆上十分消耗CPU資源。

以上只對(duì)Block進(jìn)行了說明,那么對(duì)于__block 變量又是怎么處理的?

當(dāng)Block從棧上復(fù)制到堆上時(shí),其擁有的所有__block 變量也會(huì)隨Block全部被復(fù)制到堆上。棧上的__block變量結(jié)構(gòu)體中的成員變量 __forwarding的值會(huì)被替換成堆上的__block變量結(jié)構(gòu)體的地址,堆上的__block變量結(jié)構(gòu)體中的成員變量 __forwarding依然指向自身,因此,不管是在棧上我們都能通過__forwarding 訪問同一個(gè)__block 變量。

image

Block 循環(huán)引用

Block 是如何引起循環(huán)引用的

#import <Foundation/Foundation.h>

typedef void (^printBlock_t)(void);

@interface TestCircleRetain : NSObject {
    printBlock_t printBlock;
}
@property (nonatomic,weak) NSString *name;
- (void)test;

@end

#import "TestCircleRetain.h"

@implementation TestCircleRetain

- (instancetype)init {
    self = [super init];
    if (self) {
        self.name = @"sss";
    
        printBlock = ^{
            NSLog(@"print: %@", _name);
        };
    }
    return self;
}


- (void)test {
    
    printBlock();
}

- (void)dealloc {

    NSLog(@"TestCircleRetain dealloc");
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        TestCircleRetain *testCircleRetain = [TestCircleRetain alloc] init];
    }
    return 0;
}


@end

運(yùn)行后可發(fā)現(xiàn)TestCircleRetain實(shí)例類的dealloc方法沒有調(diào)用,因?yàn)樵赥estCircleRetain實(shí)例方法中,Block里使用了 帶有 __strong(強(qiáng)引用) 修飾的testCircleRetain(也就是self)對(duì)象的成員變量 name,所以Block會(huì)捕獲testCircleRetain(而不是只捕獲name,即使你用的是_name,和self.name并無差別),并且當(dāng)Block賦值給成員變量printBlock時(shí),Block由棧上復(fù)制到了堆上 ,因此 testCircleRetain 持有 printBlock,printBlock 持有 testCircleRetain,雙方互相持有(強(qiáng)引用),沒法銷毀,故而沒法執(zhí)行dealloc()方法。不過上面的循環(huán)引用比較明顯,編譯器會(huì)發(fā)現(xiàn)并警告。

image
如何避免循環(huán)引用
  • 使用 __weak(弱引用)修飾的變量

上面的例子中,Block捕獲了帶有 __strong修飾的testCircleRetain對(duì)象,導(dǎo)致testCircleRetain持有的printBlock變量強(qiáng)引用了testCircleRetain自身引起循環(huán)引用,我們可以使用 __weak 修飾的變量并將testCircleRetain(即self)賦值使用來避免循環(huán)引用。

__weak TestCircleRetain *weakSelf = self;
printBlock = ^{
    NSLog(@"print: %@", weakSelf.name);
};
image
  • 使用__block 變量來避免循環(huán)引用
__block block_self = self;
printBlock = ^{
    NSLog(@"print: %@", block_self.name);
    block_self = nil;
};

使用__block修飾的變量并賦值self(testCircleRetain自身),它們之間的引用便變成了:testCircleRetain 引用了 printBlock,block_self 引用了 testCircleRetain和 printBlock。當(dāng)printBlock執(zhí)行后 循環(huán)引用便會(huì)打破,但是如果你不使用 printBlock的話,便會(huì)持續(xù)循環(huán)引用從而導(dǎo)致內(nèi)存泄漏。

image
最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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