block 分类
block可以分配在栈和堆上,也可以是全局的。分配到栈上的块可以拷贝到堆中,同标准的Objective-C对象一样,具备引用计数
类型 |
描述 |
环境 |
NSGlobalBlock |
全局Block,保存在数据区(.data段) |
定义在全局区或者没有访问自动局部变量 |
NSStackBlock |
栈Block,保存在栈区 |
访问了自动局部变量 |
NSMallocBlock |
堆Block, 保存在堆区 |
__NSStackBlock__调用了copy |
block 修饰符
在ARC下 block 属性可以用 copy 或者 strong 来修饰。
为什么用copy来修饰?
因为block变量默认声明为栈变量,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆。
block本质是对象,可以retain和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作用域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃。在对block进行copy后,block存放在堆区。
Xcode 4.2之后,引入了ARC机制,在一些默认的情况下系统会帮你自动调用copy操作:
- block作为函数返回值
- 将block赋值给strong指针
- block作为某些系统方法参数
系统的整体思路是如果block被释放,有潜在的异常风险时,手动“帮你”copy下
block 声明格式
标准格式
// block声明
returnType (^blockName)(parameters);
// block赋值
^returnType(parameters) {
// do something;
};
// 示例
int (^sumBlock)(int a, int b) = ^int(int a, int b) {
return a + b;
};
int sum = sumBlock(1, 1);
省略格式
赋值时可省略返回类型和block名称,甚至入参
// 有返回值有入参
int (^sumBlock)(int, int) = ^(int a, int b) {
return a + b;
};
// 无返回值无入参
void (^printBlock)(void) = ^{
NSLog(@"Hello World!");
};
typedef 简化声明
typedef returnType (^blockName)(parameters);
typedef int (^SumBlock)(int a, int b);
SumBlock block = ^(int a, int b) {
return a + b;
};
变量捕获
OC block 变量捕获
- 默认情况
捕获基础值时,是将值拷贝到闭包内,闭包内外不相互影响。
捕获指针时,是将指针值拷贝到闭包内,指针值不变所以指向同一对象,闭包内外可共同修改同一对象的内部值。
- 添加
__block
修饰后
捕获基础值时,是将值的指针传递到闭包内,闭包内外相互影响。
捕获指针时,是将指针的指针传递到闭包内,闭包内外可共同修改同一对象的内部值(指针指向的对象内部),还能让指针指向新的对象(修改指针本身)
Swift 闭包变量捕获
- 默认情况
捕获引用,相当于 OC 添加__block
修饰
- 添加捕获列表
拷贝值,相当于 OC 中默认情况
OC与Swift闭包对比总结
循环引用
类持有 block,block 通过 self 持有类,从而循环引用(因为 copy 修饰,block 本质是位于堆区的对象)
解决方法:block 外先 weak 持有,block 内再 strong 持有
// ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^completionBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.name);
};
}
@end
隐式的内存泄漏,block内虽然没有直接引用self,但是_name表示viewController的变量,要找到它就需要持有self,因此形成了隐式的内存泄漏。
// ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^completionBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.completionBlock = ^{
NSLog(@"%@", _name);
};
}
@end
链式调用写法
// Calculator.h
@interface Calculator : NSObject
@property (nonatomic) CGFloat result;
- (Calculator *(^)(CGFloat addend))add;
@end
// Calculator.m
#import "Calculator.h"
@implementation Calculator
- (Calculator *(^)(CGFloat addend))add {
return ^(CGFloat addend) {
self.result += addend;
return self;
};
}
@end