Block实现原理

主要介绍block的类型和底层分析

block类型

block主要由三种类型

  • NSGlobalBlock:全局block,存储在全局区
void(^block)(void) = ^{
    NSLog(@"LTD");
};
NSLog(@"%@", block);

当前block内没有捕获外部变量,属于全局block。

截屏2021-07-16 上午11.49.58.png
  • NSMallocBlock:堆区block,因为block既是函数,也是对象
        int a = 10;
        void(^testBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        testBlock();
        NSLog(@"%@",testBlock);

当前block会捕获外界变量,是堆区block。


截屏2021-07-16 下午1.15.42.png
  • NSStackBlock:栈区block
        int a = 10;
        void(^__weak testBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        NSLog(@"%@",testBlock);

如果不用__weak修饰变量,那么该变量就是NSMallocBlock。但是通过__weak修饰变量后,那该变量类型就是栈区block

截屏2021-07-16 下午1.30.15.png

总结:

  • block没有捕获变量到block内,那block直接存储在全局区
  • 如果block捕获变量
    • 此时block被强引用,则block存储在堆区,即堆区block
    • 此时block被弱引用(通过__weak修饰),则block存储在栈区,即栈区block
截屏2021-07-12 上午9.55.02.png
截屏2021-07-12 上午9.55.15.png

block底层分析

通过clang分析不同类型的block底层数据结构,怎么捕获外部变量。

数据结构

创建一个block

   int a = 10;
   void(^testBlock)(void) = ^{
      NSLog(@"%d",a);
   };
   NSLog(@"%@",testBlock);

通过 clang -rewrite-objc main.m -o mian.cpp 生成main.cpp文件,查看编译后的数据格式

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_1,testBlock);

    }
    return 0;
}

去除类型转换的括号,简化为

 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);

block的数据类型为__main_block_impl_0,是一个结构体,同时可以说明block是一个__main_block_impl_0类型的对象,这也是为什么block能够%@打印的原因。

//block结构体类型
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
//block代码块的描述
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)};

//block方法的结构体类型
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_0,a);
        }

//block代码块的结构体类型
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

总结:
block的本质是一个结构体,其内部包含block基本的数据结构代码块的结构体方法访问的外部变量。因为block数据结构内部有isa指针,所以block本质也是对象。由于block函数没有名称,也被称为匿名函数

1、block为什么需要调用

block底层的类型为__main_block_impl_0结构体,通过其同名构造函数创建,第一个传入的block的内部实现代码块,即__main_block_func_0,用fp表示,然后赋值给implFuncPtr属性,然后在main中进行了调用。如果不调用,block内部实现的代码块将无法执行,可以总结为以下两点

  • 函数声明:即block内部实现声明成了一个函数__main_block_func_0
  • 执行具体的函数实现:通过调用blockFuncPtr指针,调用block的代码块执行

2、block是如何获取外界变量的

block捕获外部值类型

捕获外部int是不能进行修改

   int a = 10;
   void(^testBlock)(void) = ^{
      NSLog(@"%d",a);
   };
   NSLog(@"%@",testBlock);

编译后代码分析

int a = 10;
// 初始化block结构体 传入a
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        
//去除类型转换简化后 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);


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

__main_block_impl_0初始化时,使用初始化列表来初始化字段设置结构体捕获的变量a值为10。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy 值拷贝,即 a = 10,此时的a与传入的__cself的a并不是同一个

        printf("CJL - %d", a);
}

执行block代码块,其内部重新初始变量a赋值。
总结:
block捕获外界变量时,在内部会自动生成同一个属性来保存,代码块内部会copy该属性执行。

block捕获外部对象

初始化一个数组,block捕获修改数组成功


截屏2021-07-16 下午3.56.05.png

编译后代码分析

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableArray *array = __cself->array; // bound by copy

            ((void (*)(id, SEL, ObjectType _Nonnull, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("setObject:atIndexedSubscript:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), (NSUInteger)0);
        }

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSMutableArray * array = ((NSMutableArray *(*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("initWithObjects:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 8), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 9), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 10), __null);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_0,array);
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_1,array);

    }
    return 0;
}
  • __main_block_impl_0初始化是传入数组,赋值给属性array
  • __main_block_func_0代码块内操作捕获数组进行修改。

总结:

  • 捕获的外界对象(强引用),操作原对象。

__block的原理

变量a进行__block修饰,然后在block中对a进行++操作

        __block int a = 10;
        void(^testBlock)(void) = ^{
            a++;
            NSLog(@"%d",a);
        };
        testBlock();
        NSLog(@"%@",testBlock);

通过底层编译如下

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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

            (a->__forwarding->a)++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_0,(a->__forwarding->a));
        }

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_1,testBlock);

    }
    return 0;
}
  • 在main函数内部先初始化一个__Block_byref_a_0结构体,__Block_byref_a_0初始时,将&a强转类型为__Block_byref_a_0结构体,再赋值到__forwarding
  • __main_block_impl_0初始化时传入__Block_byref_a_0(&a),赋值给__Block_byref_a_0 *a属性(强引用)
  • __main_block_func_0代码块内,通过__cself->a读取捕获的参数对象,再读取属性结构体内__forwarding(&a),取值进行处理。实则是操作捕获同一个内存空间

总结:

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

推荐阅读更多精彩内容