iOS中的Block徹底詳解

1、Block基礎介紹

一、概念介紹

Block又稱爲塊或塊對象,它是蘋果在OSX10.6和iOS4.0中新加入的功能,是C語言層面的特性及功能實現,相似其它語言的閉包(closure)功能.當時蘋果正使用LLVM的clang做爲C語言的編譯器來改進C/OC/C++/OC++等的編譯處理,Block也是當時的重要成果.html

二、塊的定義及語法

Block是帶有自動變量(局部變量)的匿名函數.由於底層結構體實現有isa指針,也被看做是塊對象.Block的出現實際上是爲了代替指針函數的一種語法結構,以前傳遞狀態須要使用不透明的void指針來傳遞狀態,而Block把C語言特性所編寫的代碼封裝成簡明且易用的接口.git

下面是Block的實現語法結構: block的語法是脫字符^加花括號,花括號裏是塊的實現.塊能夠當成是變量的值使用.以下:github

^{
    //Block implementation
}
複製代碼

Block類型的聲明語法結構:面試

return_type (^block_name)(parameters)
//中間的block_name能夠看做是變量名
複製代碼

Block的調用:xcode

block_name(parameters)
複製代碼

2、Block的關於捕獲變量的底層實現

在塊聲明的範圍裏,全部變量均可覺得其所捕獲.捕獲的自動變量不可修改,修改要加__block前綴. 塊總能修改實例變、靜態變量、全局變量、全局靜態變量,無需加__block.安全

咱們常常會看到block相關的文章或面試題中有這些內容.那麼Block底層實現是怎麼樣的呢?bash

一、咱們首先來看一下Block的內存佈局,以下:

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved; //預留內存大小
    unsigned long int size; //塊大小
    void (*copy)(void *dst, void *src); //指向拷貝函數的函數指針
    void (*dispose)(void *); //指向釋放函數的函數指針
};


struct Block_layout {
    void *isa; //指向Class對象
    int flags; //狀態標誌位
    int reserved; //預留內存大小
    void (*invoke)(void *, ...); //指向塊實現的函數指針
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
複製代碼

其中最重要的就是invoke函數指針和descriptor塊的描述.invoke函數指針它指向了塊的實現,它的void*參數傳入的是塊的結構體. descriptor的結構體中包含了塊大小以及兩個重要的輔助函數指針等.咱們注意到塊的佈局中,最下面一部分是捕獲到的變量,前面提到的void*參數傳入塊的結構體也是爲了拿到捕獲到的變量.那麼下面讓咱們來看一下塊的實際源碼是什麼樣的:閉包

cd到目錄下,在終端經過 clang -rewrite-objc 文件名 的方法將文件轉換爲C/C++代碼.若是發現不能轉換須要下載插件,指令爲xcode-select --install.app

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int ivar = 1;
        void (^tmpBlock)(void) = ^{
            NSLog(@"tmpBlock:%d",ivar);
        };
        tmpBlock();
    }
    return 0;
}
複製代碼

轉換後點開目錄下的.cpp文件,會看到上萬行的代碼,屏蔽掉無用的代碼,直接找到主要代碼以下:函數

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;
  int ivar;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _ivar, int flags=0) : ivar(_ivar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int ivar = __cself->ivar; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_459ed6_mi_2,ivar);
        }

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 ivar = 1;
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}
複製代碼

咱們來挨個看一下:

__block_impl 結構體是包含咱們剛剛在Block佈局中提到的前四個成員變量.

__main_block_impl_0結構體是包含了__block_impl結構體變量和__main_block_desc_0結構體指針以及捕獲到的變量.最後是初始化構造函數中對impl成員和desc進行賦值.

__main_block_func_0這個函數他傳遞了__main_block_impl_0*型的參數,並從中讀取塊結構體中捕獲的參數進行實際操做.看到這裏你有個明白了這個就是對應上面那個invoke函數指針所指向的函數.

__main_block_desc_0這個結構體就是上面所說的塊的描述結構體.可能你會發現他少了兩個輔助函數的函數指針,緣由後面會說.

最後是main函數中的具體初始化和函數調用.恰好對應塊變量的聲明實現部分和塊的調用.

二、關於捕獲自動變量的分析

捕獲自動變量這部分首先咱們要明確有哪幾種變量,以下:

  • auto自動變量:默認方法的做用域中不加前綴就是自動變量,並且ARC下默認還會加__strong.
  • static靜態變量:存放在內存的可讀寫區,初始化只在第一次執行時起做用,運行過程當中語句塊變量將保持上一次執行的值.
  • static全局靜態變量:也放在可讀寫區,做用域爲當前文件.
  • 全局變量:也在可讀寫區,整個程序可用.
  • 另外在OC中它們又分爲對象類型和非對象類型.

下面讓咱們看一下他們在Block中的表現. 首先是前面那個是用自動變量ivar的例子,若是咱們修改它的值編譯器會報警告:

Variable is not assignable (missing __block type specifier) 複製代碼

提示咱們不能修改變量,這是爲何呢?咱們來打印一下block內外的指針變量的地址:

int ivar = 1;
        NSLog(@"塊外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            NSLog(@"塊內%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:20:40.063273+0800 LearningDemo[69358:5789849] 塊外0x7ffeefbff4dc
2019-12-05 18:20:40.063840+0800 LearningDemo[69358:5789849] 塊內0x103400ad0
複製代碼

你會發現它們不是同一個地址,是的,block的__main_block_impl_0結構體拷貝了一份自動變量進去做爲結構體的成員變量,你修改的是結構體內部的ivar的值,而不是外部ivar的值,他們並非同一塊內存上的東西.蘋果讓編譯器在這種狀況下報警告,提示開發者是改不了的. 接下來咱們給它加一個static前綴,以下:

static int ivar = 1;
        NSLog(@"塊外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            ivar = 2;
            NSLog(@"塊內%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:41:36.083804+0800 LearningDemo[69801:5806136] 塊外0x100003320
2019-12-05 18:41:36.084770+0800 LearningDemo[69801:5806136] 塊內0x100003320
複製代碼

有意思的事情發生了,能夠修改變量了,並且地址竟然同樣了~那咱們看一下轉換後的源碼:

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

            (*ivar) = 2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_3,&(*ivar));
        }

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 ivar = 1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_2,&ivar);
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_4,tmpBlock);
    }
    return 0;
}
複製代碼

從上面的代碼能夠看到,它捕獲到的變量變成了指針變量int *ivar,而後再看打印的代碼中的&(*ivar),它拿到的是指針變量中所存的地址,也就是說結構體捕獲的指針變量存儲的地址和Block外部的靜態變量是同一個地址!而Block實現的賦值是經過*運算符引用變量來賦值的,像這樣(*ivar) = 2;.到這裏你應該已經明白爲何能夠改變外部變量了吧,由於它們訪問的是同一個內存地址.總結一下就是地址訪問是能夠修改外部變量的. 那你可能會說,對象類型本質上不就是一個指針變量嗎,爲何不能修改,咱們以NSString*爲例看一下它轉換後的實現源碼:

NSString * strvar = @"strvar";
            NSLog(@"塊外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                NSLog(@"塊內%p",&strvar);
            };
            tmpBlock();

2019-12-05 19:01:07.019544+0800 LearningDemo[69970:5816051] 塊外0x7ffeefbff4d8
2019-12-05 19:01:07.020200+0800 LearningDemo[69970:5816051] 塊內0x100701780
複製代碼

從上面的結果可知,地址是不同的,爲何不同呢 看一下源碼就知道了

NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_ca150a_mi_4,&strvar);
複製代碼

從源碼咱們得知他取得的是指針變量的地址,而非指針變量存儲的地址,因此咱們給對象類型賦值實際上是賦值一個新的對象地址上去,而不是經過同一個地址去修改替換分配在堆上的對象. 當咱們加上static前綴以後發現能夠修改了,實際上是由於Block捕獲的是NSString **strvar;指針的指針,也就是存的是外部對象類型變量的地址而不是變量內存儲的地址,因此咱們能夠修改strvar了.

NSMutableString * strvar = [NSMutableString stringWithFormat:@"NSMutableString"];
            NSLog(@"塊外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                [strvar appendString:@"111"];
                NSLog(@"塊內%p",&strvar);
            };
            NSLog(@"tmpBlock %@",strvar);
            tmpBlock();
            NSLog(@"tmpBlock %@",strvar);

2019-12-06 12:01:25.339194+0800 LearningDemo[81031:6194158] 塊外0x7ffeefbff4d8
2019-12-06 12:01:25.339677+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString
2019-12-06 12:01:25.339739+0800 LearningDemo[81031:6194158] 塊內0x102841ef0
2019-12-06 12:01:25.339780+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString111
複製代碼

你會發現若是是可變對象的話是能夠經過方法來修改堆上的對象的,也就是說只要能夠經過同一個地址訪問到變量就能夠對其賦值修改等.

全局變量靜態全局變量由於做用域很廣,在它們的做用域裏均可以訪問,因此並不須要捕獲,均可以Block內修改,下面是測試代碼:

int globalVar = 1;
static int staticGlobalVar = 1;
NSString *globalStr = @"globalStr";
static NSString *staticGlobalStr = @"staticGlobalStr";

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"塊外%p",&globalVar);
        NSLog(@"塊外%p",&staticGlobalVar);
        NSLog(@"塊外%p",&globalStr);
        NSLog(@"塊外%p",&staticGlobalStr);
         void (^tmpBlock)(void) = ^{
             globalVar += 1;
             staticGlobalVar += 1;
             globalStr = @"修改globalStr";
             staticGlobalStr = @"修改staticGlobalStr";
             NSLog(@"塊內%p",&globalVar);
             NSLog(@"塊內%p",&staticGlobalVar);
             NSLog(@"塊內%p",&globalStr);
             NSLog(@"塊內%p",&staticGlobalStr);
         };
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
        tmpBlock();
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
    }
    return 0;
}
2019-12-06 13:54:10.126960+0800 LearningDemo[82231:6240152] 塊外0x100003308
2019-12-06 13:54:10.127585+0800 LearningDemo[82231:6240152] 塊外0x100003320
2019-12-06 13:54:10.127687+0800 LearningDemo[82231:6240152] 塊外0x100003310
2019-12-06 13:54:10.127738+0800 LearningDemo[82231:6240152] 塊外0x100003318
2019-12-06 13:54:10.127774+0800 LearningDemo[82231:6240152] globalVar: 1
2019-12-06 13:54:10.127802+0800 LearningDemo[82231:6240152] staticGlobalVar: 1
2019-12-06 13:54:10.127847+0800 LearningDemo[82231:6240152] globalStr: globalStr
2019-12-06 13:54:10.127891+0800 LearningDemo[82231:6240152] staticGlobalStr: staticGlobalStr
2019-12-06 13:54:10.127970+0800 LearningDemo[82231:6240152] 塊內0x100003308
2019-12-06 13:54:10.128051+0800 LearningDemo[82231:6240152] 塊內0x100003320
2019-12-06 13:54:10.128112+0800 LearningDemo[82231:6240152] 塊內0x100003310
2019-12-06 13:54:10.128170+0800 LearningDemo[82231:6240152] 塊內0x100003318
2019-12-06 13:54:10.128221+0800 LearningDemo[82231:6240152] globalVar: 2
2019-12-06 13:54:10.128271+0800 LearningDemo[82231:6240152] staticGlobalVar: 2
2019-12-06 13:54:10.128365+0800 LearningDemo[82231:6240152] globalStr: 修改globalStr
2019-12-06 13:54:10.128870+0800 LearningDemo[82231:6240152] staticGlobalStr: 修改staticGlobalStr
複製代碼

三、關於_ _block的底層實現原理

咱們理解上面那些內容以後接下來正式講解__block的原理:

__block int ivar = 1;
                NSLog(@"塊外%p",&ivar);
                void (^tmpBlock)(void) = ^{
                    ivar += 1;
                    NSLog(@"塊內%p",&ivar);
                };
                ivar += 1;
                NSLog(@"ivar1: %d",ivar);
                tmpBlock();
                NSLog(@"ivar2: %d",ivar);
                NSLog(@"tmpBlock %@",tmpBlock);

2019-12-06 14:23:05.294076+0800 LearningDemo[82616:6257418] 塊外0x7ffeefbff4d8
2019-12-06 14:23:05.294589+0800 LearningDemo[82616:6257418] ivar1: 2
2019-12-06 14:23:05.294673+0800 LearningDemo[82616:6257418] 塊內0x102c00a78
2019-12-06 14:23:05.294726+0800 LearningDemo[82616:6257418] ivar2: 3
2019-12-06 14:23:05.295064+0800 LearningDemo[82616:6257418] tmpBlock <__NSMallocBlock__: 0x102c009f0>
複製代碼

將上面代碼轉換成底層實現源碼:

struct __Block_byref_ivar_0 {
  void *__isa;  //對象特性
__Block_byref_ivar_0 *__forwarding;//棧上指向本身,若是結構體被拷貝到堆上指向堆上的拷貝的__Block_byref_ivar_0.
 int __flags;//狀態標準位
 int __size;//大小
 int ivar;//原變量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_ivar_0 *ivar; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_ivar_0 *_ivar, int flags=0) : ivar(_ivar->__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_ivar_0 *ivar = __cself->ivar; // bound by ref

                    (ivar->__forwarding->ivar) += 1;
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_3,&(ivar->__forwarding->ivar));
                }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->ivar, (void*)src->ivar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->ivar, 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_ivar_0 ivar = {(void*)0,(__Block_byref_ivar_0 *)&ivar, 0, sizeof(__Block_byref_ivar_0), 1};
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_2,&(ivar.__forwarding->ivar));
                void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));
                (ivar.__forwarding->ivar) += 1;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_4,(ivar.__forwarding->ivar));
                ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_5,(ivar.__forwarding->ivar));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_6,tmpBlock);
    }
    return 0;
}
複製代碼

會發現多了一個 __Block_byref_ivar_0 結構體,這個結構體就是__block修飾的變量要轉換的結構體, __main_block_impl_0捕獲的就是這個結構體指針,Block的實現函數__main_block_func_0中經過__forwarding這個成員變量指向本身或堆上的拷貝的結構體,而後再訪問成員變量中的原變量ivar來修改值. ivar打印的地址不一樣是由於__Block_byref_ivar_0結構體被拷貝到了堆區,天然地址也不一樣,至於爲何會被拷貝到堆區,後面會講.

接下來再看一下對象類型變量加__block前綴的代碼及轉換的源碼:

__block NSArray *arr = @[@"1"];
        
           void (^tmpBlock)(void) = ^{
                arr = @[@"2"];
           };
        tmpBlock();
複製代碼
struct __Block_byref_arr_0 {
  void *__isa;
__Block_byref_arr_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSArray *arr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_arr_0 *arr; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_arr_0 *_arr, int flags=0) : arr(_arr->__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_arr_0 *arr = __cself->arr; // bound by ref

                (arr->__forwarding->arr) = ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_1).arr, 1U);
           }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->arr, (void*)src->arr, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arr, 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_arr_0 arr = {(void*)0,(__Block_byref_arr_0 *)&arr, 33554432, sizeof(__Block_byref_arr_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_0).arr, 1U)};

           void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_arr_0 *)&arr, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}
複製代碼

會發現__Block_byref_arr_0結構體多了兩個函數指針,它們分別指向__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131.再看上面的__block修飾的非對象類型和這個對象類型它們的__main_block_desc_0都有copy和dispose函數指針,它們都指向了__main_block_copy_0和__main_block_dispose_0函數.這些函數是幹什麼的?接下來讓咱們一探究竟.

這裏提一下,Block在使用的時候會分紅棧塊(_NSConcreteStackBlock)、堆塊(_NSConcreteMallocBlock)、全局塊(_NSConcreteGlobalBlock).下面的代碼也會根據塊的類型進行判斷,這裏提一下方便理解代碼.具體介紹會放在第三節.

下面的代碼是Block拷貝時底層執行的代碼:

//ARC下給Block變量賦值的時候會調用
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//
id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block. Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) {
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
複製代碼

經過上面的代碼及註釋咱們能夠了解到了Block拷貝的基本流程,裏面有一些枚舉值,以下:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
複製代碼

經過註釋得知這些枚舉值用於描述塊對象的Block_layout-> flags的值.沒錯就是上面void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));中傳的實參570425344.咱們來打印一下按位邏輯或的值NSLog(@"%d",1 << 25 | 1 << 29); LearningDemo[43493:8342154] 570425344 恰好是傳的那個值,也就是BLOCK_HAS_COPY_DISPOSE| BLOCK_USE_STRET.也就是代碼會繼續走(*desc->copy)(result, aBlock);方法, 而後就調用到了咱們剛剛所提到的__main_block_copy_0.而__main_block_copy_0中的_Block_object_assign,它到底幹了什麼,下面是它源碼:

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /******* id object = ...; [^{ object; } copy]; ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* void (^object)(void) = ...; [^{ object; } copy]; ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/

        *dest = object;
        break;

      default:
        break;
    }
}
複製代碼

根據上面拷貝邏輯可知,該函數傳的參數destArg是拷貝到堆上的目標Block的成員變量, object是源Block的成員變量,也就是說該函數主要是針對捕獲變量的操做.最後的flags也是個枚舉,以下:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ... 對象類型變量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable 塊變量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable __Block_byref_arr_0結構體
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers 弱引用變量
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines. 
};
複製代碼

接下來看看這些枚舉值的case中都作了什麼.

BLOCK_FIELD_IS_OBJECT中執行了_Block_retain_object,它的實現就是下面的代碼,因此它其實什麼都沒作.而後將object的地址賦值給拷貝後的block成員變量.

static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
複製代碼

BLOCK_FIELD_IS_BLOCK當塊中捕獲到了Block變量時的操做,又會去調用_Block_copy函數.

BLOCK_FIELD_IS_BYREF也就是__block修飾變量拷貝的操做,也就是__main_block_copy_0函數傳的那個8/*BLOCK_FIELD_IS_BYREF*/,而後內部又調用了下面函數

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
複製代碼

而其中的131則是128+3.

下面看看執行的_Block_byref_copy函數,源碼以下:

// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 👇 拷貝Block_byref結構體
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
 //****👆上面這段代碼就是棧上的Block成員變量forwarding指向堆上的Block的過程****//

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//👈初始化__Block_byref_arr_0的時候,傳的33554432恰好是 1<<25打的值.
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
複製代碼

上面的代碼執行順序就是 塊拷貝->__main_block_copy_0 ->__Block_byref_id_object_copy_131

剩下的case都是指向源變量地址.

到此爲止你應該已經發現一開始的例子__main_block_desc_0中沒有copy和dispose函數指針的緣由就是它捕獲的是非對象類型,而有這兩個函數指針的是對象類型和__block修飾的變量,用於拷貝捕獲的變量.另外,__block修飾的非對象類型是沒有__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131的,由於__Block_byref做爲對象是須要這些拷貝操做的,而裏面的非對象類型變量不須要. 另外關於_Block_object_dispose的底層實現我就不在贅述了,和copy過程相似,看一下源碼就懂了.

3、關於棧塊、堆塊、全局塊

根據Block所在內存區域不一樣分爲棧塊(_NSConcreteStackBlock)、堆塊(_NSConcreteMallocBlock)、全局塊(_NSConcreteGlobalBlock).它們在ARC和MRC下的表現是有差別的.

  • MRC 下的表現 堆塊:爲Block執行copy方法纔會變成堆塊.賦值到有copy、strong修飾符的屬性變量也是堆塊. 棧塊:寫在方法裏的默認就是棧塊. 全局塊:全局變量區域定義的塊.不賦值且捕獲了自動變量的也是全局塊.
  • ARC下的表現 堆塊:塊實現賦值到塊自動變量上就會變成堆塊.由於默認有__strong修飾符的緣故,被持有. 棧塊:不賦值的塊且捕獲了自動變量的默認是棧塊. 全局塊:全局變量區域寫的塊.塊裏面使用的是全局變量、全局靜態變量、靜態變量、或者不使用任何變量的時候也是全局塊.

瞭解當前的Block是那種類型的塊以及如何會變成這種類型的塊以後,再結合上面的關於Block捕獲變量的底層實現和拷貝過程咱們就清楚的明白當前的塊能不能修改變量,變量有沒有執行拷貝了. #4、常見實戰應用

一、適配器模式

用Block做爲適配器來傳值,能夠下降代碼分散程度,在數據種類不是不少的時候能夠代替委託協議來使用.被適配者在Block的實現中執行操做.相對協議更加輕鬆的實現了適配器模式.

二、循環引用問題

通常咱們會用__weak 來修飾self變量來打破循環引用,由於__weak修飾的變量Block不會持有它,執行拷貝操做引用計數也不會增長,可是在Block的實現內記得用__strong再修飾一邊self變量,防止外面的變量提早釋放或者被置空致使訪問錯誤.原理也很簡單,由於Block的結構體會捕獲self,加上__weak修飾符就能夠不持有self變量來,也就不會形成循環引用.而__strong是加在Block的實現裏的,也不會形成循環引用,又能夠保證代碼的安全訪問.

歡迎你們關注個人我的博客: darkknightkazuma.github.io

相關文章
相關標籤/搜索