Block探究

在我们实际的开发过程中,block的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子,能熟练的使用block是不是就意味着你真正的了解block呢?正所谓知其然,知其所以然。

  • 基本使用

    block.png

    1.无参无返回值的block

void (^block)() = ^{
    NSLog(@"无参数无返回值的block");
};
block();

2.有参无返回值的block

void (^block)(int) = ^(int a){
      NSLog(@"有参数无返回值的block  %d",a);
 };
 block(5);

3.有参有返回值的block

int (^block)(int) = ^(int a){
     NSLog(@"有参数有返回值的block  %d",a);
     return a + 5;
 };
 block(5);
  • 作为属性的使用
typedef void (^sumBlock)(int,int);

@property (nonatomic, copy) sumBlock sumBlc;

   self.sumBlc = ^(int a,int b){
        NSLog(@"sum a + b  = %d",a+b);
    };
    
    self.sumBlc(3,5);
  • 作为函数调用
- (void)blockTest{
    NSString *(^stringBlc)(int) = ^(int a){
        // 需要定义的操作
        return [NSString stringWithFormat:@"%dabc",a];
    };
    NSString * str1 = stringBlc(123);
    NSString * str2 = stringBlc(345);
    NSString * str3 = stringBlc(456);
}
  • 底层实现

简单的介绍了一下block的使用,接下来我们就看看它的底层实现到底是什么样子的,首先创建一个Mac os程序,在main.m函数中写一个简单block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(int , int) = ^(int a, int b){
            NSLog(@"this is my block a = %d , b = %d",a,b);
            NSLog(@"this age is %d",age);
        };
        block(5,5);
    }
    return 0;
}

然后进入相应的文件夹,在终端输入clang -rewrite-objc main.m,发现多了一个main.cpp的文件,这是一个9w多行的代码,在文件的最下面我们发现了main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                                 (void*)__main_block_func_0, 
                                                 &__main_block_desc_0_DATA,
                                                 age)
       //如果block内部没有用到age,只有前两个参数
       //__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DAT)
        );

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

我们发现__main_block_impl_0这个函数传入了3个参数__main_block_func_0, &__main_block_desc_0_DATA, age,并且将函数的地址赋给了block,那我们看一下__main_block_impl_0

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

__main_block_impl_0是一个结构体,它里面有两个结构体:impl和 Desc,age 和一个构造函数,并且将传入的__main_block_func_0赋值给了impl.FuncPtr,将传入的&__main_block_desc_0_DATA赋值给了Desc,在构造函数中为age完成了赋值,这一狗仔函数主要是为这两个结构体赋值,那么我们接下来看看这两个结构体。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

impl是一个__block_impl类型的结构体,里面有一个isa指针,这个isa的赋值是block的地址,所以说block实质上是一个OC对象,Flags=0,FuncPtr是传入的__main_block_func_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)};

Desc是一个__main_block_desc_0类型的结构体,里面只有reserved和Block_size,而__main_block_desc_0_DATA的作用就是保存了这个block的大小。

最后就只剩下__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_0,a,b);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_1,age);
        }

这个函数首先传入了a,b两个参数,从__cself中取出block代码块要用的age,然后是两个NSLog,那我们可以看出这个__main_block_func_0的作用就是保存了block的代码块实现。

至此,__main_block_impl_0中出现的函数和结构体我们都已经看过了,我们可以大致分析如下:

  • block的底层是一个__main_block_impl_0的函数,这个函数会传入3个参数(没有用到外部变量是2个):分别是__main_block_func_0&__main_block_desc_0_DATA 和用到的外部变量,将__main_block_func_0赋值给impl中的FuncPtr__main_block_desc_0_DATA赋值给Desc,age在构造函数中赋值。
  • __main_block_func_0中保存的是block定义的代码块,__main_block_desc_0_DATA中保存的是block的大小。
 void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                                 (void*)__main_block_func_0, 
                                                 &__main_block_desc_0_DATA,
                                                 age)

它们之间的关系可以用下图表示:


block的结构图.png

那我们看看block在这么调用的

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                                (__block_impl *)block, 5, 5);

由于block指向的是__main_block_impl_0的地址,而impl是__main_block_impl_0结构体中的第一个成员,里面保存着block的代码块,将block转化为(__block_impl *)类型去调用block的代码块。

  • __block做了什么

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        void (^block)(int , int) = ^(int a, int b){
            NSLog(@"this is my block a = %d , b = %d",a,b);
            NSLog(@"this age is %d",age);
            age = 20;
            NSLog(@"this age is %d",age);
        };
        
        block(5,5);
    }
    return 0;
}
this is my block a = 5 , b = 5
this age is 10
this age is 20

我们知道如果age没有用__block修饰的话,在block中直接修改age的值编译器直接会报错,加上就可以直接修改age的值,那__block到底做了些什么?
将main.m文件clang之后我们发现以下不同:

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
                                    (void*)0,
                                    (__Block_byref_age_0 *)&age, 
                                     0, 
                                     sizeof(__Block_byref_age_0), 
                                     10
                              };
void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                    (void *)__main_block_func_0, 
                                    &__main_block_desc_0_DATA, 
                                    (__Block_byref_age_0 *)&age, 
                                    570425344)
                              );

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                    (__block_impl *)block, 5, 5);
struct __Block_byref_age_0 {
  void *__isa;          // 0
__Block_byref_age_0 *__forwarding; // 指向自己的指针
 int __flags;           // 0
 int __size;            // __Block_byref_age_0的大小
 int age;               // age
};

首先定义了一个__Block_byref_age_0类型的结构体age,__forwarding指向结构体age自己,里面封装了外部变量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;
  }
};

__main_block_impl_0内部有一个__Block_byref_age_0类型的age,在构造函数中_age->__forwarding指向了结构体自己。

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_0,a,b);
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_1,
                  (age->__forwarding->age));
       (age->__forwarding->age) = 20;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_2,
                  (age->__forwarding->age));
        }

首先取出__main_block_impl_0中的结构体age,在修改age的值得时候,先通过__forwarding找到结构体age的地址,再去修改age中真正的age对象的值。

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};

因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)void (*dispose)(struct __main_block_impl_0*),利用OC的Runtime进行内存管理。
这里的_Block_object_assign_Block_object_dispose就对应着retain和release方法。

_Block_object_assign函数调用时机及作用

当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。
_Block_object_assign函数会自动根据__main_block_impl_0结构体内部的age是什么类型的指针,对age对象产生强引用或者弱引用。

_Block_object_dispose函数调用时机及作用

当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。
_Block_object_dispose会对age对象做释放操作,类似于release,也就是断开对age对象的引用,而age究竟是否被释放还是取决于age对象自己的引用计数。

iOS底层原理总结 - 探寻block的本质
深入研究Block捕获外部变量和__block实现原理

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

推荐阅读更多精彩内容