关于iOS的block的使用网上有很多文章讲的很详细,这里不再做相应的阐述,我们在这里主要针对以下几点和大家进行探讨,欢迎指正。
一、block的声明和定义
block的一般声明形式
block变量的声明格式为:返回值类型 (^block名字)(参数列表)
// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);
// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);
复制代码
我们一般使用typedef定义block类型
// 定义一种无返回值无参数列表的Block类型
typedef void(^SayHello)();
// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
SayHello hello = ^(){
NSLog(@"hello");
};
// 调用后控制台输出"hello"
hello();
复制代码
二、block的内存管理
block的三种类型
- 全局块(_NSConcreteGlobalBlock)
- 栈块(_NSConcreteStackBlock)
- 堆块(_NSConcreteMallocBlock)
block是怎么在内存中进行存放的
- block不访问外界变量
block既不在栈中也不在堆中,此时为全局块,ARC和MRC下都是如此
- block访问外界变量
MRC环境下:访问外接变量的block默认存在栈中。
ARC环境下:访问外界变量的block默认存放在堆中,实际是先放在栈区,在ARC情况下自动拷贝到栈区,又自动释放。
block使用copy修饰符
- block使用copy修饰符的目的
使用copy修饰符的作用就是将block从栈区拷贝到堆区
- 为什么要这么做
上面是官方文档截图,里面解释了block为什么这样做的目的。复制到堆区的主要目的就是保存block的状态,延长器生命周期。因为如果block在栈上的话,其所书的变量族谱用于结束,该block就会被释放掉,block中的__block变量也同时被释放掉。为了解决栈块在其变量作用域结束之后被释放掉的问题,所以将block复制到堆中。
- 不同类型的block的拷贝方法效果
block使用__block修饰符
- __block有什么作用
/// 函数1
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
//调用结果输出42
复制代码
/// 函数2
- (void)testMethod {
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
//输出结果为50
复制代码
上面的例子可以看出,使用__block修饰的变量,可以block中进行访问,变量修改后,block可以获取修改后的值。
- 为什么使用__block之后就可以修改block外部变量
在第一个函数中,block会把anInterger变量复制为自己私有的const变量,在anInteger = 50的操作的时候,block已经将其复制未自己私有变量,所以修改变量值,对block中的anInteger不会造成任何影响。
在第二个函数中,aninteger是一个局部变量,存储在栈区。给anInteger加入__block修饰符所起到的作用就是只要观察到该变量被block所持有,就将该变量在栈中的内存地址放到堆中,此时不管block外部还是内部anInterger的内存地址都是一样的,进而不管在block外部还是内部都可以修改anInterger变量的值,所以anInteger = 50之后,在block输出的值就是50了。
三、循环引用
- 循环引用的情况以及解决办法
循环引用的情况
self.someBlock = ^(Type var){
[self dosomething];
};`
复制代码
解决办法 (1)ARC下:使用__weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
复制代码
(2)MRC下:使用__block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};
复制代码
- 异步并发执行的时候使用__strong关键字
先看一段代码
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption(抢占) weakSelf turned nil
[weakSelf doAnotherThing];
};
}
复制代码
这段代码看似正常,但是在并发执行的时候,block的执行是可以抢占的,而且对weakSelf指针的调用时序不同可以导致不同的结果,比如在一个特定的时序下weakSelf可能会变成nil,这个时候在执行doAnotherThing就会造成程序的崩溃。为了避免出现这样的问题,采用__strong的方式来进行避免,更改后的代码如下:
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething]; // strongSelf != nil
// 在抢占的时候,strongSelf还是非nil的。
[strongSelf doAnotherThing];
};
}
复制代码
- 使用系统API 使用系统的block api 不会产生循环引用,例如:
[UIView animateWithDuration:0.5 animations:^{
[self doSomething];
}];
复制代码
在这种情况下是不需要考虑循环引用的,因为这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用。
- 避免循环引用总结
- 在block不是作为一个property的时候,可以在block里面直接使用self,比如UIView的animation动画block。
- 当block被声明为一个property的时候,需要在block里面使用weakSelf,来解决循环引用的问题。
- 当和并发执行相关的时候,当涉及异步的服务的时候,block可以在之后被执行,并且不会发生关于self是否存在的问题。