block 底层实现是结构体。
block 本质是对象,更具体的说,是函数以及执行上下文封装起来的对象。
block底层结构体中有isa(指针),所以block是oc对象;底层结构体中有函数指针,block可传入参数,返回值。
常见变量:局部变量、全局变量、局部静态变量、全局静态变量;
block传入局部变量,是值截取;传入局部静态变量,是指针截取;传入全局变量或全局静态变量,直接获取。(__block 修饰的变量,是指针截取)
block分为:局部、全局、静态blcok,
block存放划分:栈block(存放栈stack区)、堆block(存放堆heap区)、全局的block(存放在初始化数据.data区);
不使用外部变量block,是全局block;
使用外部变量,不进行copy,是栈block;
栈block进行copy,是堆block。
验证:
不使用外部变量的 是 全局block
代码:
NSInteger num = 10;
NSLog(@"%@",[^{ } class]);
运行结果:
__NSGlobalBlock__
使用外部变量,没有进行copy , 是 栈block
代码
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"%zd",num);//使用外部变量
} class]);
运行结果:
__NSStackBlock__
使用外部变量,进行copy , 是 堆block
代码
NSInteger num = 10;
dispatch_block_t block;
NSLog(@"*%@",[block class]);//null
block = ^{
NSLog(@"%zd",num);//使用外部变量
};
NSLog(@"*%@",[block class]);
运行结果:(如果不使用外部变量,block 是全局变量)
__NSMallocBlock__
代码验证练习:
-(void)testWithBlock:(dispatch_block_t) block{
// block();
NSLog(@"-%@",[block class]);// -__NSGlobalBlock__ block | __NSStackBlock__ 如果没有使用外部变量,是全局block,如果使用外部变量,没有进行copy,是栈block;
block = ^(void){
NSLog(@"***");
NSLog(@"%@",self); //模拟使用外部变量
};
// block();
NSLog(@"-%@",[block class]);// -__NSGlobalBlock__ block | __NSMallocBlock__如果没有使用外部变量,是全局block,如果使用外部变量,有进行copy,是堆block;
}
-(void)btnClick{
NSInteger num = 10;
NSLog(@"%@",[^{ } class]);//__NSGlobalBlock__ 不使用外部变量的 是 全局block
NSLog(@"%@",[^{
NSLog(@"---%zd",num);
} class]); //__NSStackBlock__ 使用外部变量,没有进行copy , 是 栈block
[self testWithBlock:^{
NSLog(@"----");
NSLog(@"%@",[self class]); //模拟使用外部变量
}];
}
此处分割线-----------------------------------------
分析栈block 进行copy(赋值)后,打印结果是堆block:
代码:
-(void)testWithBlock:(dispatch_block_t) block{
block();
NSLog(@"%@",[block class]);//__NSStackBlock__
dispatch_block_t newBlock = block;
NSLog(@"block is %@ ,\n newBlock is %@",[block class],[newBlock class]);
// block is __NSStackBlock__ ,
// newBlock is __NSMallocBlock__
}
-(void)btnClick{
NSInteger num = 3;
[self testWithBlock:^{
NSLog(@"%zd",num);//调用外部变量,栈block
}];
}
打印结果:
__NSStackBlock__
block is __NSStackBlock__ ,
newBlock is __NSMallocBlock__
此时,由代码打印结果不难发现,栈block 还是栈block,不是变成堆block或者消失;栈block进行copy,newBlock 是堆block,原来的栈block还是栈block。
总结:对全局block 进行copy,得到的还是全局block,因为已经初始化的,不发生改变,代码如下1;对栈block进行copy,copy在堆区,代码略(上边代码已讲解);对堆区block进行copy,copy还是在堆区,有文章说此时引用计数加一,没有得到验证,代码如下2。
代码1:
-(void)testWithBlock:(dispatch_block_t) block{
block();
NSLog(@"%@",[block class]);
dispatch_block_t newBlock = block;
NSLog(@"block is %@ ,\n newBlock is %@",[block class],[newBlock class]);
}
-(void)btnClick{
NSInteger num = 3;
[self testWithBlock:^{
NSLog(@"----");//不调用外部变量,全局block
}];
}
代码1打印结果:
__NSGlobalBlock__
block is __NSGlobalBlock__ ,
newBlock is __NSGlobalBlock__
代码2:
-(void)testWithBlock:(dispatch_block_t) block{
block();
dispatch_block_t newBlock = block;
dispatch_block_t new1Block = newBlock;
NSLog(@"block is %@ , newBlock is %@ , new1Block is %@",[block class],[newBlock class],[new1Block class]);
NSLog(@"block count = %ld",CFGetRetainCount((__bridge CFTypeRef)(block)));
NSLog(@"newBlock count = %ld",CFGetRetainCount((__bridge CFTypeRef)(newBlock)));
NSLog(@"new1Block count = %ld",CFGetRetainCount((__bridge CFTypeRef)(new1Block)));
}
-(void)btnClick{
NSInteger num = 3;
[self testWithBlock:^{
NSLog(@"%zd",num);//调用外部变量,栈block
}];
}
打印结果:
3
block is __NSStackBlock__ , newBlock is __NSMallocBlock__ , new1Block is __NSMallocBlock__
block count = 1
newBlock count = 1
new1Block count = 1
分割线----------------------------------------------------------------------------------------
__block 修饰,对于block截获的外部局部变量赋值,需要使用__block修饰,全局变量不需要__block修饰。
代码分析:
代码:.h文件和.m文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BlockTest : NSObject
@end
NS_ASSUME_NONNULL_END
#import "BlockTest.h"
@implementation BlockTest
-(void)test0{
__block NSInteger num = 20;
void(^block)(void) = ^{
num+=1;
NSLog(@"%zd",num);
};
block();
}
@end
终端执行命令:
clang -rewrite-objc BlockTest.m
得到.cpp文件,
分析
使用__block 修饰变量num。block截获num指针,并且生成一个新的结构体对象,这里“__forwarding”指向栈block。
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
NSInteger num;
};
__block 中,“__forwarding”指针总结:
在__block 修饰的变量进行赋值时,栈上的“__forwarding”指针指向堆上的“__forwarding”指针,堆上的“__forwarding”指针,指向其自身,对__block修饰的变量进行修改,其实是修改堆上的__block变量。“__forwarding”指针确保无论存放位置在栈还是堆,最终指向同一个变量。
__block 修饰self 造成的循环引用示例:
由于block截取__block修饰变量会持有该变量,当:1__block 修饰self,2且self持有block时,3同时block内部使用到__block修饰的self时,123步骤造成多循环引用,即 self持有block;block持有__block修饰变量;而__block修饰变量持有self,导致内存泄露。
解决方法1:主动断开__block修饰变量对self的持有,可在block内部使用__blockself 后,将其置空(nil);但如果block一直不被调用,循环将一直存在,无法断开。
解决方法2:使用__weak 修饰self。
__block typeof(self) _weakSelf = self;
dispatch_block_t block;
block = ^{
NSLog(@"%@",_weakSelf);
};
block();
使用工具Instruments - Leaks 检查如图: