一、是什么
Block本质上也是一个OC对象,底层也是一个结构体,内部也有isa指针。
封装了函数调用以及函数调用环境的OC对象。
底层结构如 图所示:
二、block的基本使用
2.1 block的形式
- 无参无返回值
//定义一个block
void (^myblock1)()=^(){
NSLog(@"myblock1");
};
//使用block
myblock1();
- 无参有返回值
int (^myblock2)()=^(){
return 8;
NSLog(@"myblock2");
};
myblock2();
- 有参无返回值
void (^myblock3)(int,int)=^(int a, int b){
NSLog(@"%d",a+b);
};
myblock3(3,7);
//也可以先定义变量,再赋值
myblock3 =^(int x,int y){
NSLog(@"%d",x-y);
};
myblock3(8,7);
- 有参有返回值
int (^myblock4)(int,int)=^(int a, int b){
NSLog(@"%d",a+b);
return a + b;
};
int sum = myblock4(3,4);
NSLog(@"%d",sum);
三、block的typedef
利用typedef定义block类型(和指向函数的指针很像)
- 格式
typedef 返回值类型 (^新别名)(参数类型列表);
typedef int (^myblock)(int , int);
以后就可以利用这种类型来定义block变量了;
typedef void (^Block)();
Block b1;
//Block类型的变量
b1 = ^{
NSLog(@"myblock");
};
//有参数有返回值
typedef int (^newType)(int , int);
newType nt1 = ^(int a, int b){
retutn a +b;
};
int s = nt1 (12 , 23);
NSLog(@"%d",s);
//联系定义多个newType的变量
newType n1,n2,n3
n1 = ^(int x, int y){
retutn x*y;
};
四、block对变量的捕获
//auto 可以不写,默认的值,离开作用域会自动销毁
// auto int age = 10;
int a = 10;//传的变量,block内部存储的是个变量
static int b = 10;//传的地址值,block内部存储的是个地址值
void (^block)(void) = ^{
NSLog(@"捕获的值局部变量a=%d,静态变量b=%d,全局变量=%d",a,b,c);
};
a = 20;
b = 30;
c = 40;
block();
//1、为什么局部变量需要捕获,全局变量不需要捕获?
// 因为在底层代码实现里面: 局部变量需要跨函数访问,所以需要捕获,而全局变量在哪个函数都可以直接访,不需要捕获。
//1、为什么2者有这的差异?
//auto类型代码执行完就要销毁,所以是值传递,不然再去访问就会出现问题,因为局部变量已释放。
// 而静态变量一直在内存中, 不会销毁,所以是指针传递
void (^block1)(void) = ^{
NSLog(@"self=%p",self);
};
block1();
// self也会被捕获
2021-09-18 15:16:46.086277+0800 OCStudy[90772:4443186] 捕获的值a=10,b=30
代码内部实现:
五、block的类型
block 有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
-
MRC环境下的Block类型:
打开MRC
void (^block)(void) = ^{
NSLog(@"hello");
};
NSLog(@"block的类型=%@",[block class]);
NSLog(@"block的父类=%@",[[block class] superclass]);
NSLog(@"block的父类的父类=%@",[[[block class] superclass] superclass]);
block的类型=__NSGlobalBlock__
block的父类=NSBlock
block的父类的父类=NSObject
void (^block)(void) = ^{//__NSGlobalBlock__
NSLog(@"hello");
};
NSLog(@"block的类型=%@",[block class]);
int a = 10;
void (^block1)(void) = ^{//__NSStackBlock__
NSLog(@"age=%d",a);
};
NSLog(@"block1的类型=%@",[block1 class]);
NSLog(@"block2的类型=%@",[[block1 copy] class]);//__NSMallocBlock__
NSLog(@"block3的类型=%@",[[block copy] class]);//__NSGlobalBlock__
2021-09-20 15:50:48.324228+0800 dddd[50990:5734435] block的类型=__NSGlobalBlock__
2021-09-20 15:50:48.325460+0800 dddd[50990:5734435] block1的类型=__NSStackBlock__
2021-09-20 15:50:48.325632+0800 dddd[50990:5734435] block2的类型=__NSMallocBlock__
2021-09-20 15:50:48.325756+0800 dddd[50990:5734435] block3的类型=__NSGlobalBlock__
所以在MRC时代,block用Copy修饰。
将栈的数据拷贝到堆上,防止局部变量的数据释放掉以后,block还去访问无数据的问题。
-
ARC环境下的Block类型:
ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,有以下情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- cocoa API中,将block作为参数的,比如GCD的函数
void (^block)(void) = ^{//__NSGlobalBlock__
NSLog(@"hello");
};
int a = 10;
void (^block1)(void) = ^{//__NSMallocBlock__
NSLog(@"age=%d",a);
};
NSLog(@"block1的类型=%@",[block1 class]);
2021-09-20 16:15:44.560817+0800 OCStudy[51711:5753274] block的类型=__NSGlobalBlock__
2021-09-20 16:15:44.561154+0800 OCStudy[51711:5753274] block1的类型=__NSMallocBlock__
六、 __block修改局部auto变量
__block不能修饰局部变量、静态变量
编译器会将__block变量包装成一个对象
__block int age = 10;//会将age包装成一个对象
NSLog(@"age的值=%d,地址=%p",age,&age);
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
age = 20;
NSLog(@"age的值=%d,地址=%p",age,&age);
// arr = nil;//这是修改值
[arr addObject:@"123"];//不是 修改指针,是使用指针
[arr addObject:@"456"];
};
block();
NSLog(@"age的值=%d,地址=%p",age,&age);
NSLog(@"arr的值=%@",arr);
七、 循环引用
typedef void (^TestBlock)(void)
@interface Person
@property (nonatomic , strong) TestBlock testblock;
@end
-(void)dealloc{
NSLog(@"person-delloc,死了");
}
- 不使用weakself的情况。
Person *person = [[Person alloc] init];
person.age = 100;
person.testblock = ^{
NSLog(@"age2 = %d",person.age);
};
person.testblock();
NSLog(@"end");
person和block相互强引用,造成循环引用,所以person对象无法dealloc。
- 使用weakself
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
};
person.testblock();
NSLog(@"end");
由于一个使用__weak修饰,破除了相互强引用,所以peron可以delloc
- 来看下面一段代码 ,在block内部,有一个2秒后需要执行的代码,也需要用到age的值
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age2 = %d",weakPerson.age);
});
};
person.testblock();
NSLog(@"end");
我们发现后面再去使用age的值时,person已经死掉了,这个时候可以使用使用__strong
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
__strong __typeof(weakPerson)strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age2 = %d",strongPerson.age);
});
};
person.testblock();
NSLog(@"end");
在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
八、关于block的问题
1、怎么解决block循环引用的问题?
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
});
2、什么时候在 block里面用self,不需要使用weakself?
block和其他的对象之间不会相互强引用
比如UIView的动画代码,我们在使用UIView animateWithDuration:方法做动画的时候,并不需要使用weakself,因为引用持有关系是:
UIView 的某个负责动画的对象持有block,block 持有了self因为 self 并不持有 block,所以就没有循环引用产生,因为就不需要使用 weak self 了。
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
3、为什么 block 里面还需要写一个 strong self,如果不写会怎么样?
在 block 中先写一个 strong self,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。
我们以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代码举例:
__weak__typeof(self)weakSelf =self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong__typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus= status;
if(strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}};
如果没有 strongSelf 的那行代码,那么后面的每一行代码执行时,self 都可能被释放掉了,这样很可能造成逻辑异常。
4、block 里 strong self 后,block 不是也会持有 self 吗?而 self 又持有 block ,那不是又循环引用了?
__weak __typeof(self)weakSelf = self; //1
[self.context performBlock:^{
[weakSelf doSomething]; //2
__strong __typeof(weakSelf)strongSelf = weakSelf; //3
[strongSelf doAnotherSomething];
}];
1.使用__weak __typeof是在编译的时候,另外创建一个局部变量weak对象来操作self,引用计数不变。block 会将这个局部变量捕获为自己的属性,访问这个属性,从而达到访问 self 的效果,因为他们的内存地址都是一样的
2.因为weakSelf和self是两个变量,doSomething有可能就直接对self自身引用计数减到0了,所以在[weakSelf doSomething]的时候,你很难控制这里self是否就会被释放了.weakSelf只能看着
3.__strong __typeof在编译的时候,实际是对weakSelf的强引用.指针连带关系self的引用计数会增加.但是你这个是在block里面,生命周期也只在当前block的作用域.所以,当这个block结束, strongSelf随之也就被释放了.不会影响block外部的self的生命周期.
5、关于Masonry里面的Block,为什么不需要使用?
关于Masonry里面的Block:函数参数里面的Block是局部的block(栈上),block内部引用self不会造成循环引用;是否会循环引用只看函数内部是否copy了这个block(比如把它付给全局的Block)
6、block的原理是怎样的?本质是什么?
Block本质上也是一个OC对象,底层也是一个结构体,内部也有isa指针。
封装了函数调用以及函数调用环境的OC对象
7、__block的作用是什么?有什么使用注意点?
_block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
注意:循环引用问题
8、block的属性修饰词为什么是copy?
在MRC环境下,block用Copy修饰。将栈的数据拷贝到堆上,防止局部变量的数据释放掉以后,block还去访问无数据的问题。
在ARC环境下,使用strong修饰也可以,编译器会根据情况自动将栈上的block复制到堆上
9、block在修改NSMutableArray,需不需要添加__block?
不需要
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
arr = nil; 这是修改值
[arr addObject:@"123"]; 不是 修改指针,是使用指针进行操作
[arr addObject:@"456"];
};