block原理

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

一.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的底层代码结构如图


微信图片_20221018120037.png

截屏2022-03-24 下午3.05.14.png


block的底层结构如下图所示


微信图片_20221018120043.png

image.png


二.block的变量捕获


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


微信图片_20221018120046.png

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 )


微信图片_20221018120050.png

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变量值


微信图片_20221018120053.png

image.png


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


微信图片_20221018120057.png

image.png


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


微信图片_20221018120100.png

image.png


微信图片_20221018120104.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为什么指向自身的指针


微信图片_20221018120107.png

image.png


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


微信图片_20221018120111.png

image.png


八.__block的内存管理


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


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


微信图片_20221018120114.png

image.png


当block从堆中移除时


会调用block内部的dispose函数->dispose函数内部会调用__Block_object_dispose函数-> __Block_object_dispose函数会自动释放引用的__block变量(release)


微信图片_20221018120118.png

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函数来释放它们


相关文章
|
XML 移动开发 监控
mPaaS常见问题之这边真机调试和真机预览都会报 {"stats"如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
374 0
|
9月前
|
机器学习/深度学习 人工智能 自然语言处理
Qwen2.5-VL-32B:阿里开源多模态核弹!32B模型吊打自家72B,数学推理封神
阿里巴巴最新开源的Qwen2.5-VL-32B多模态模型,在数学推理、视觉问答等任务中超越前代72B版本,支持图像细粒度理解和复杂逻辑分析,已在HuggingFace开源。
1360 0
Qwen2.5-VL-32B:阿里开源多模态核弹!32B模型吊打自家72B,数学推理封神
|
9月前
|
存储 安全 API
认证支持全面碾压?Apipost的OAuth2.0与ASAP实战演示,Apifox用户看完扎心了
认证缺失的隐秘危机,你可能正在“裸奔”调试。开发者常忽视认证机制,导致API请求未携带合法令牌、OAuth2.0配置错误等问题,轻则调试失败,重则引发安全漏洞。Apifox在OAuth2.0和ASAP协议支持上存在缺陷,而Apipost不仅覆盖12种主流认证类型,还实现了OAuth2.0全流程自动化及ASAP秒级配置,重新定义API调试的安全边界。
|
9月前
|
设计模式 存储 前端开发
HarmonyOS Next 浅谈 发布-订阅模式
本文浅谈 HarmonyOS Next 中的发布-订阅模式,通过 ArkTS 的 Emitter 对象实现事件的持续订阅、单次订阅、取消订阅和触发功能。文章详细介绍了设计目标、接口定义及具体实现步骤,包括类型定义、类结构设计和调用示例。发布-订阅模式有助于系统解耦与扩展,适用于工具封装和游戏开发等场景。文末附有效果图和总结,帮助开发者更好地理解和应用该模式。
237 14
HarmonyOS Next 浅谈 发布-订阅模式
|
10月前
|
人工智能 算法
大模型不会推理,为什么也能有思路?有人把原理搞明白了
大模型(LLMs)在推理任务上表现出与人类不同的问题解决思路。最新研究《Procedural Knowledge in Pretraining Drives Reasoning in Large Language Models》发现,大模型通过合成程序性知识来完成推理任务,而非简单检索答案。这为理解其推理能力提供了新视角,并指出了改进方向,如设计更有效的算法和使用更大规模数据。论文链接:https://arxiv.org/abs/2411.12580。
317 3
|
12月前
|
存储 关系型数据库 对象存储
体验云数据库RDS通用云盘核心能力
本次课程由杨浩磊(木信)分享,主题为体验云数据库RDS通用云盘的核心能力。内容分为四部分:1) 初识RDS通用云盘,介绍其低成本、高性能的特点;2) 核心能力详解,涵盖IO加速、IO突发和数据归档功能;3) 方案及应用案例,展示实际性能提升与成本优化;4) 线上活动与权益,提供免费试用等优惠。RDS通用云盘通过多级存储架构,显著提升读写性能并降低存储成本,适用于多种业务场景。
463 38
|
12月前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
API 区块链
获取指定网页基础信息【TDK】免费API接口教程
该接口用于从标准网页中提取标题、关键词、描述和图标等信息。支持POST/GET请求,需提供用户ID、KEY及目标网址等参数,可选指定访问节点。返回状态码、信息提示及提取的内容。示例与详细文档见官网。
307 4
|
11月前
|
关系型数据库 分布式数据库 PolarDB
[PolarDB实操课] 02.使用云起实验室资源快速体验PolarDB分布式版
本次课程由阿里云PolarDB开源架构师黄心雨分享,重点介绍如何使用云起实验室资源快速体验PolarDB分布式版。主要内容包括: 1. **PolarDB-X的四种安装方法**:Docker、PXD工具、Kubernetes和源码编译。 2. **容器技术简介**:解释容器在云原生环境中的作用,解决代码跨环境迁移问题。 3. **云起实验室实操**:通过云起实验室提供的零门槛平台,快速部署PolarDB-X,体验其主要功能。 4. **课程小结**:总结PolarDB-X的安装方式及实际操作步骤,并展望后续课程内容。
155 0
|
存储 数据挖掘 关系型数据库
DataFrame 与数据库交互:从导入到导出
【5月更文挑战第19天】本文介绍了如何在数据分析中实现DataFrame与MySQL数据库之间的数据交互。通过`pandas`的`read_sql`函数可将数据库中的数据导入DataFrame,处理后使用数据库游标执行插入或更新操作将数据导回。注意数据类型匹配、数据完整性和一致性。对于大量数据,可采用分块读取和批量操作提升效率。这种交互能结合数据库的存储管理和DataFrame的分析功能,提高数据处理效率。
503 2