1. 程式人生 > >iOS block探究(二): 深入理解

iOS block探究(二): 深入理解

你要知道的block都在這裡

上一篇文章iOS block探究(一):基礎詳解介紹了block的基本原理和使用方法,以及相關修飾符詳解。
本文將會深入底層探究block的本質。

三種block型別

NSGlobalBlock

如果block不捕獲外部變數,那麼在ARC環境下就是建立一個全域性block。全域性block儲存在全域性記憶體中,不需要在每次呼叫的時候都在棧中建立,塊所使用的整個記憶體區在編譯期已經確定了,因此這種塊是一種單例,不需要多次建立。

NSMallocBlock

如果block捕獲外部變數,那麼在ARC環境下就是創了一個堆區block。程式碼中最常用的block

也就是堆區block,當堆區block的引用計數為0時也會像普通物件一樣被銷燬,再也不能使用了。

NSStackBlock

在MRC環境下,預設建立棧區block,一般使用copy函式拷貝到堆區再使用,否則block可能會被釋放,在ARC環境下一般不考慮。

深入程式碼理解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; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };

上述結構體中最重要的就是invoke變數,從宣告中可以看出,這是一個函式指標,指向block的執行程式碼,可以認為block的執行程式碼是一個匿名函式,在建立block

的時候傳遞給了invoke變數。
struct Block_layout結構體中有一個descriptor變數,而struct Block_descriptor比較重要的就是copy函式和dispose函式,從命名就可以看出,copy函式用於捕獲變數並持有引用,而dispose函式就是用於釋放捕獲的變數。
block捕獲的變數都會儲存在結構體struct Block_layout的後面,對於物件儲存的是指標,在invoke函式執行之前全部讀出。
以上就是block大致的實現方式,可以看出,block是一種替換函式指標的語法,相比使用函式指標更方法,寫法也更便捷。

接下來看一下具體程式碼的實現。
在進入正題之前,先介紹一個clang編譯器的命令

clang -rewrite-objc main.m
這個命令用於clang重寫.m檔案.cpp檔案

先實現一個最簡單的無引數無返回值的block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^printBlock)() = ^{
            NSLog(@"Hello World");
        };
        printBlock();
    }
    return 0;
}

使用上述命令生成.cpp檔案後可以找到如下程式碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

      NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
     void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
     ((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
    }
    return 0;
}

static __NSConstantStringImpl __NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello World",11};

上述程式碼中__main_block_func_0函式就是建立block時定義的一個函式,當block執行時就是執行了該函式,這個函式內部是呼叫了另一個函式,也就是block裡寫的執行程式碼,可以看出,block實際就是將我們定義的block又封裝了一下,使用起來更方便。

接下來修改程式碼,讓block捕獲一個物件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *name = @"Jiaming Chen";
        void (^printBlock)() = ^{
            NSLog(@"Hello World %@", name);
        };
        printBlock();
    }
    return 0;
}

再次使用clang重寫程式碼後可以看到如下定義

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

      NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_1, name);
     }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->name, 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;
     NSString *name = (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_0;
     void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, name, 570425344));
     ((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
    }
    return 0;
}

struct __main_block_impl_0中可以看到裡面儲存了被捕獲的物件,同時在__main_block_func_0函式中將捕獲的物件賦值給了上述結構體變數。並且增加了__main_block_copy_0函式和__main_block_dispose_0函式,分別用於持有物件和釋放物件。

再看一下__block的使用會有什麼區別。
修改程式碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSUInteger age = 22;
        void (^printBlock)() = ^{
            NSLog(@"Hello World %ld", age);
            age = 100;
        };
        printBlock();
    }
    return 0;
}

重寫生成的程式碼如下:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 NSUInteger 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_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_ea236c_mi_0, (age->__forwarding->age));
      (age->__forwarding->age) = 100;
     }
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), 22};
     void (*printBlock)() = ((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 *)printBlock)->FuncPtr)((__block_impl *)printBlock);
    }
    return 0;
}

上述程式碼可以看出,使用__block修飾後會生成一個struct __Block_byref_age_0的結構體,可以看出,__block修飾後會捕獲變數的引用而不是進行值拷貝,這也就是為什麼block內部可以修改__block修飾的變數以及外部變數修改後會影響block內部捕獲變數的原因了。

備註

由於作者水平有限,難免出現紕漏,如有問題還請不吝賜教。