block原理

一.block本质

block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象

对如下代码转换成c++代码进行分析
// auto:自动变量,离开作用域就销毁.如果不写默认就是auto
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
         // age的值捕获进来(capture)
         NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
block();

转成c++得出block的底层代码结构如图
截屏2022-03-24 下午3.05.14.png

block的底层结构如下图所示


image.png
二.block的变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
image.png
三.block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

NSLog(@"%@:%@:%@",[block class],[[block class] superclass],[[[block class] superclass] superclass]);
打印结果为__NSGlobalBlock__:NSBlock:NSObject

NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )

image.png

block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock 调用了copy
1.没有访问auto
void (^block)(void) = ^{
};
NSLog(@"%@",[block class]);//打印结果:__NSGlobalBlock__
2.访问了auto
auto int age = 10;
void (^block)(void) = ^{
    NSLog(@"%@",age);
};
NSLog(@"%@",[block class]);//mrc环境打印结果:NSStackBlock 。ARC环境打印结果:__NSMallocBlock__,ARC环境下系统会自动调用copy到堆上。
3.NSStackBlock调用了copy
void (^block)(void) = [^{
            NSLog(@"%d",age);
} copy];

每一种类型的block调用copy后的结果如下所示

block的类 副本源的配置储存域 复制效果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteMallocBlock 引用计数增加
四.block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到推上,比如
1.block作为函数返回值时,ARC会自动进行copy操作;

typedef void(^MyBlock)(void);
MyBlock returnBlock(){
    //调用方法在栈上,调用完后被销毁
    int age =10;
    void (^block)(void) = ^{
        NSLog(@"%d",age);
    };
    return ^{
        NSLog(@"%d",age);
    };
}
当调用returnBlock()时相当于 block赋值给了MyBlock(产生强指针),这时候会对block进行copy
void(^MyBlock)(void) = ^{
        NSLog(@"%d",age);
};

2.将block赋值给(强指针)__strong指针时,ARC会自动进行copy操作;

typedef void(^MyBlock)(void);
void test(void){
    //myblock强引用block
    MyBlock myblock = ^{
        NSLog(@"%d",age);
    };//ARC会对block自动进行copy操作
}

3.block作为方法名中包含usingBlock的方法参数时

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
}];

4.block作为GCD的方法参数时,ARC会自动进行copy操作;

static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
});
五.block的使用

MRC环境下block属性写法(使用copy)
@property (copy, nonatomic) void (^block)(void);
ARC环境下block写法(可以使用copy、strong)
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

六.当block访问auto或者__block修饰的对象类型的变量时

block在栈上(没有copy操作):将不会对变量产生强引用
block在被拷贝到堆上(已有copy操作):1.会调用block内部的copy函数。2copy函数内部会调用_Block_object_assign函数。3_Block_object_assign函数会根据auto变量的修饰符(__strong强指针、__weak弱指针、__unsafe_unretained不是强引用)做出相应的操作,形成强引用或者弱引用(注意:ARC时auto和__block都会retain,MRC时auto会retain但__block不会retain)
block从堆上移除:1.会调用block内部dispose函数。2.dispose函数内部会调用_Block_object_dispose函数。3._Block_object_dispose函数会自动释放引用的auto变量(release)
如下源码分析

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
//如果访问的是对象类型就会自动生成这两个函数,对他内部需要访问的对象类型变量进行内存管理操作return或
release
//block在被拷贝到堆上时会调用copy函数,对变量形成强引用或弱引用
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
//block从堆上移除时会调用block内部dispose函数释放变量release
  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};
函数 调用时机
copy函数 栈上的block复制到堆时
dispose函数 堆上的block被废弃时
七.__block修饰符

block内部无法修改auto变量值

image.png

1.使用__block修饰后可以修改auto变量的值
image.png

2.__block不能修饰全局变量,静态变量
image.png
image.png

3.编译器会将__block修饰过的变量包装成对象

__block int age = 10;
void (^block)(void) = ^{
        age = 23;
};
age = 123;
转成c++代码
__Block_byref_age_0 age = {0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
(age.__forwarding->age) = 123;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // 包装后的age对象
};

struct __Block_byref_age_0 {
  void *__isa;//对象isa
  __Block_byref_age_0 *__forwarding;//指向自己的指针
   int __flags;//标识
   int __size;//结构体所占内存大小
   int age;//age变量
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; //在结构体__main_block_impl_0中找到__Block_byref_age_0 *age对象;
  (age->__forwarding->age) = 23;
}

__forwarding为什么指向自身的指针


image.png

从下图可以看出如果在栈区__forwarding会指向栈区结构体地址如果复制到堆后__forwarding会指向堆区结构体地址


image.png
八.__block的内存管理

当block在在栈上时,并不会对__block变量产生强引用,没有调用copy函数还在栈上,栈上的变量随时会销毁

当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用
image.png

当block从堆中移除时

会调用block内部的dispose函数->dispose函数内部会调用__Block_object_dispose函数-> __Block_object_dispose函数会自动释放引用的__block变量(release)
image.png
.auto变量、__block修饰对象类型的变量时

1.当block在栈上时,对他们都不会产生强引用
2.当block拷贝到堆上时,都会通过copy函数来处理它们

_Block_object_assign((void*)&dst->per1, (void*)src->per1, 3/*BLOCK_FIELD_IS_OBJECT*/);auto对象类型变量
(flag = 8 BLOCK_FIELD_IS_OBJECT ),当copy到堆上时,block会调用了[obj retain]方法,引用计数器会+1, 否则复制到堆上时,会对找不到该变量(因为局部变量受作用域影响,肯定比block的生命周期短),且在复制的过程中,通过fowarding指针可以找到堆上的该变量。

_Block_object_assign((void*)&dst->per2, (void*)src->per2, 8/*BLOCK_FIELD_IS_BYREF*/);加了__block对象类型变量
flag = 3 (BLOCK_FIELD_IS_BYREF),即使复制多次也只会`复制一次`,后面只是将该变量的`引用计数器+1`(但其实对于基本数据类型的话,我们也不会去关心引用计数器)。不加__block的话,相当于block结构体对基本数据类型对象做了一次const化到了结构体内,只能使用,不能修改和赋值。

3.当block从堆上移除是,都会通过dispose函数来释放它们

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

推荐阅读更多精彩内容