重学OC第二十三篇:block

本文详细探讨了Block的原理,包括Block的存储域、__block变量的存储与修改、捕获对象、循环引用的处理以及Block的签名。重点分析了Block的三次复制过程和释放机制,阐述了__block变量如何在栈和堆之间转换,以及如何管理内存。
摘要由CSDN通过智能技术生成

一、Block是什么?

int main(int argc, const char * argv[]) {
   
	int a = 0;
	void (^aBlock)(void) = ^{
   
   		NSLog(@"%d", a);
	};
	aBlock();
}

用clang转为cpp
在这里插入图片描述

  • Block是带有自动变量(局部变量)的匿名函数,在OC中Block本质上就是一个对象(__main_block_impl_0结构体类比objc_object结构体)。
  • Block截获自动变量是Block语法表达式所使用的自动变量值被保存到了Block的结构体实例中。

1.1 __block结构

上面的Block中的变量a是不允许进行修改的,原因是__main_block_func_0中把a进行了值拷贝,此时修改的只是临时变量a,而不是__cself->a,会引发歧义,所以不允许更改。那__block为什么可以更改变量呢?把上面的int a = 0;用__block修饰,然后再来看clang后的代码。
在这里插入图片描述
可以看到加上__block后a变为了一个__Block_byref_a_0类型的变量,对它的操作也变成了a->__forwarding->a。具体可看下面1.2.2小节。

1.2 存储域

建议直接去看《Obj-C高级编程》书中相关内容。

1.2.1 Block存储域

将Block当作OC对象来看时,可看到它的isa指向_NSConcreteStackBlock,常用到的有3种:

设置对象的存储域 区分
_NSConcreteStackBlock 截获自动变量并且在该变量作用域内
_NSConcreteGlobalBlock 程序的数据区域(.data区) 记述全局变量的地方有Block语法时(在定义全局变量的地方定义Block);Block语法的表达式中不使用应截获的自动变量
_NSConcreteMallocBlock 当_NSConcreteStackBlock超出变量作用域ARC下大多数情况下编译器进行适当判断后调用_Block_copy拷贝到堆上

在ARC下编译器大多数情况会适当地进行判断然后自动从栈复制到堆,那编译器在什么情况下不能判断需要手动复制呢?

  • 向方法或函数的参数中传递Block时
    但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制了。以下方法或函数不用手动复制
    • Cocoa框架的方法且方法名中含有usingBlock等时
    • GCD的API
      在这里插入图片描述

下面来具体看段代码来理解

typedef void (^blk_t)(void);
NSArray *getBlockArray() {
   
    int val = 10;
    //ARC不会自动复制,需手动复制
    return [[NSArray alloc] initWithObjects:^{
   NSLog(@"blk0: %d", val);}, ^{
   NSLog(@"blk1: %d", val);}, nil];
//    return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0: %d", val);} copy], [^{NSLog(@"blk1: %d", val);} copy],nil];
}

int main(int argc, const char * argv[]) {
   
    @autoreleasepool {
   
        // insert code here...
        void (^globalBlock)(void) = ^{
    };
        //__NSGlobalBlock__
        NSLog(@"GlobalBlock is %@", globalBlock);
        
        __block int a = 10;
        void (^stackBlock)(void) = ^void {
    a++; };
        //MRC    __NSStackBlock__
        NSLog(@"StackBlock is %@", stackBlock);
        //ARC   __NSMallocBlock__
        NSLog(@"MallocBlock is %@", stackBlock);
        
        NSArray *array = getBlockArray();
        blk_t blk = (blk_t)[array objectAtIndex:0];
        blk(); //如果没有手动复制,崩溃。因getBlockArray()执行完后,栈上的Block被废弃。
    }
    return 0;
}

1.2.2 __block变量存储域

使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响。

__block变量的配置存储域 Block从栈复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

在一个Block中使用__block变量
在这里插入图片描述
在多个Block中使用__block变量
在这里插入图片描述
堆上Block被废弃,它所使用的__block变量也就被释放。
在这里插入图片描述

  • 那么__block变量用结构体成员变量__forwarding的原因是什么?
    通过Block的复制,__block变量也从栈复制到堆,此时可同时访问栈上的__block变量和堆上的__block变量。源代码如下:
__block int val = 0;
void (^blk)(void) = [^{
   ++val;} copy];
++val;
blk();
NSLog(@"%d", val);

用clang转以后
在这里插入图片描述
可以看出都是++(val.__forwarding->val),但是栈上的__block变量用结构体实例在___block从栈复制到堆上时,会将成员变量__forwording的值替换为复制目标上的block变量用结构体实例的地址。
在这里插入图片描述
通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利地访问同一个__block变量。

1.3 捕获对象

NSNumber *val = @(0);
void (^blk)(void) = ^{
   NSLog(@"%@", val);};
blk();

在这里插入图片描述
在OC中,C语言结构体不能含有附有__strong修饰符的变量,因为编译器不知道应何时时行C语言结构体的初始化和废弃操作,不能很好地管理内存。但是OC的运行时库能准确把握从栈复制到堆以及堆上的Block被废弃的时机,因此Block结构体中即使含有附有__strong修饰符或__weak修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在__main_block_desc_0结构体中增加的成员变量copy和dispose,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。
调用copy函数和dispose函数的时机:

函数 调用时机
copy函数 栈上的Block复制到堆时
dispose函数 堆上的Block被废弃时

那么什么时候栈上的Block会复制到堆呢?

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

那么问题来了,在使用__block修饰的int a时也产生了copy、dispose方法,怎么与对象区分的呢?
在这里插入图片描述

基本类型 对象 __block修饰基本类型 __block修饰对象
Block结构体 不加入copy、dispose成员变量 加入copy、dispose成员变量,flag为BLOCK_FIELD_IS_OBJECT 加入copy、dispose成员变量,flag为BLOCK_FIELD_IS_BYREF 加入copy、dispose成员变量,flag为BLOCK_FIELD_IS_BYREF
__block变量结构体 不加入copy、dispose成员变量 加入copy、dispose成员变量

1.4 Block循环引用

typedef void (^block_t)(void);
@interface TestA : NSObject

@property(nonatomic, copy) block_t aBlock;
@property(nonatomic, copy) NSString 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值