iOS Block深入原理

三種block類型

NSGlobalBlock

如果block不捕獲外部變量,那么在ARC環(huán)境下就是創(chuàng)建一個(gè)全局block。全局block存儲(chǔ)在全局內(nèi)存中,不需要在每次調(diào)用的時(shí)候都在棧中創(chuàng)建,塊所使用的整個(gè)內(nèi)存區(qū)在編譯期已經(jīng)確定了,因此這種塊是一種單例,不需要多次創(chuàng)建。

NSMallocBlock

如果block捕獲外部變量,那么在ARC環(huán)境下就是創(chuàng)了一個(gè)堆區(qū)block。代碼中最常用的block也就是堆區(qū)block,當(dāng)堆區(qū)block的引用計(jì)數(shù)為0時(shí)也會(huì)像普通對(duì)象一樣被銷毀,再也不能使用了。

NSStackBlock

在MRC環(huán)境下,默認(rèn)創(chuàng)建棧區(qū)block,一般使用copy函數(shù)拷貝到堆區(qū)再使用,否則block可能會(huì)被釋放,在ARC環(huán)境下一般不考慮。

深入代碼理解block

LLVM Block_private.h可以找到蘋果關(guān)于block的相關(guān)定義。

比較重要的定義代碼如下:

/* 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. */
};

上述結(jié)構(gòu)體中最重要的就是invoke變量,從聲明中可以看出,這是一個(gè)函數(shù)指針,指向block的執(zhí)行代碼,可以認(rèn)為block的執(zhí)行代碼是一個(gè)匿名函數(shù),在創(chuàng)建block的時(shí)候傳遞給了invoke變量。
struct Block_layout結(jié)構(gòu)體中有一個(gè)descriptor變量,而struct Block_descriptor比較重要的就是copy函數(shù)和dispose函數(shù),從命名就可以看出,copy函數(shù)用于捕獲變量并持有引用,而dispose函數(shù)就是用于釋放捕獲的變量。
block捕獲的變量都會(huì)存儲(chǔ)在結(jié)構(gòu)體struct Block_layout的后面,對(duì)于對(duì)象存儲(chǔ)的是指針,在invoke函數(shù)執(zhí)行之前全部讀出。
以上就是block大致的實(shí)現(xiàn)方式,可以看出,block是一種替換函數(shù)指針的語(yǔ)法,相比使用函數(shù)指針更方法,寫(xiě)法也更便捷。

接下來(lái)看一下具體代碼的實(shí)現(xiàn)。
在進(jìn)入正題之前,先介紹一個(gè) clang 編譯器的命令

clang -rewrite-objc main.m
這個(gè)命令用于clang重寫(xiě).m文件.cpp文件

先實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的無(wú)參數(shù)無(wú)返回值的block

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

在進(jìn)入正題之前,先介紹一個(gè)clang編譯器的命令

clang -rewrite-objc main.m
這個(gè)命令用于clang重寫(xiě).m文件為.cpp文件

先實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的無(wú)參數(shù)無(wú)返回值的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函數(shù)就是創(chuàng)建block定義的一個(gè)函數(shù),當(dāng)block執(zhí)行時(shí)就是執(zhí)行了該函數(shù),這個(gè)函數(shù)內(nèi)部是調(diào)用了另一個(gè)函數(shù),也就是 block里寫(xiě)的執(zhí)行代碼,可以看出,block實(shí)際就是將我們定義的block又封裝了一下,使用起來(lái)更方便。
接下來(lái)修改代碼,讓block捕獲一個(gè)對(duì)象

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

再次使用clang重寫(xiě)代碼后可以看到如下定義

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中可以看到里面存儲(chǔ)了被捕獲的對(duì)象,同時(shí)在__main_block_func_0函數(shù)中將捕獲的對(duì)象賦值給了上述結(jié)構(gòu)體變量。并且增加了__main_block_copy_0函數(shù)和__main_block_dispose_0函數(shù),分別用于持有對(duì)象和釋放對(duì)象。

再看一下__block的使用會(huì)有什么區(qū)別。
修改代碼如下:

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

重寫(xiě)生成的代碼如下:

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修飾后會(huì)生成一個(gè)struct __Block_byref_age_0的結(jié)構(gòu)體,可以看出,__block修飾后會(huì)捕獲變量的引用而不是進(jìn)行值拷貝,這也就是為什么block內(nèi)部可以修改__block修飾的變量以及外部變量修改后會(huì)影響block內(nèi)部捕獲變量的原因了。

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

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

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