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

你要知道的block都在这里

转载请注明出处 //www.greatytc.com/p/b6a675a7a4fa

上一篇文章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

LLVM Block_private.h可以找到苹果关于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内部捕获变量的原因了。

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,511评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,011评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,489评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,872评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,741评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,525评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,914评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,542评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,833评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,858评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,674评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,479评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,923评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,099评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,400评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,923评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,141评论 2 339

推荐阅读更多精彩内容