介绍
- block是OC中非常重要的一种技术手段
- iOS SDK4.0后,Apple大力推荐block。字面上block就是一个代码块,底层是结构体,和c语言中的函数有些类似
- block实际上就是Objective-C语言对于闭包(一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」)的实现
- block基本概念:在需要的时间调用到的可能有参数和返回值的匿名函数实现的一种数据类型
定义
从C函数和OC函数的区别来定义block
- C函数写法
int add(int num1, int num2)
- OC函数写法
-(void)show:(int num1)
- 由C到block的转变
void (^myBlock) ()
从结构来定义block
1 | struct Block_layout { |
从代码实现定义block
1 | //无参数无返回值的block |
类型
_NSConcreteGlobalBlock
全局的静态 block,不会访问任何外部变量_NSConcreteStackBlock
保存在栈中的 block,当函数返回时会被销毁(ARC环境下会被_NSConcreteMallocBlock
类型的block替代)_NSConcreteMallocBlock
保存在堆中的 block,当引用计数为 0 时会被销毁(NSConcreteMallocBlock
类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中)
变量的复制
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的,如下图所示
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的,如下图所示
下面这段代码会输出什么
1 | void demoBlock() { |
答案:输出为10。在定义block的时候,如果引用了外部变量,会对外部变量进行拷贝。记录住 在block时变量的数值。如果之后在外部修改变量的值也不会影响block内部的数值变化。
如果在block里面修改外部变量的值,会报错吗
1 | void demoBlock() { |
答案:在默认情况下,不允许block内部修改外部变量的数值。因为拷贝之后它与原数值指向了不同的地址。若是能改变则会破坏代码的可读性
需求千变万化。若我非要将x的值改成80又当如何?为什么?
1 | void demoBlock() { |
答案:如果要在block中,修改外部变量的值,需要用block修饰符号。使用了block,说明函数不关心具体数值的具体变化。
在block中,如果引用了外部使用的__block变量,block定义之后,外部变量的指针地址同样会变成堆区的地址。之所以可以修改是因为它们的指针指向的地址是相同的。
下面是一段能正确运行的代码。为啥定义成mutableString能在block内部对外部变量进行修改了?
1 | void demoBlock() { |
在block中引用外部变量要拷贝一份到堆中。这个时候拷贝到堆中的地址和栈中的地址是一样的。因此它指向“张三”在setString的时候,将指针指向的地址的值进行修改,
但是如果用stringWithString方法就会报错,因为这相当于又开辟了一个新的空间,在堆中将strM的地址指向了新的空间![]()
block的使用场景
- 枚举——通过block获取枚举对象或控制枚举进程
1 | //这个方法接受一个Block的参数,这个参数对于数组中的每个项目调用一次: |
- UIView动画——简单明了的方式规定动画
1 | [UIView animateWithDuration:0.2 animations:^{ |
- 排序——在block内写排序算法
1 | NSArray *arr = @[@"2", @"3", @"4", @"1", @"5"]; |
- 作为回调
1 | NSURLSessionDataTask *task = [self.sessionManager POST:url parameters:cmd.params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { |
- 链式编程和函数式编程:参考Masonry和ReactiveCocoa
- 简化并发任务,操作队列
1 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ |
block使用过程引发的内存泄漏
- 描述
当一个对象持有了block后,block内部又使用了这个对象或者对象的实例变量、实例方法,这样会使block也持有这个对象,这样相互持有就是循环引用,从而导致对象无法释放引起内存泄露。
- 解决
要解决循环引用只需要不要相互持有就行了,也就是说只需要一方持有就可以不让block再持有当前这个对象就OK。
1 | //循环引用,self持有了block,block又持有了self |
- __block在arc和mrc下的含义
Block === 栈上结构体实例
block === 栈上block结构体实例
如果要在ARC下, 为了防止循环引用, 使用block来修饰在Block中实用的对象,仍然会被retain, 所以需要多做一些设置 block id blockSelf=self;
self.blk=^{
NSLog(@”%@”,blockSelf);
self.blk=nil; //blk被释放, blk只有的blockSelf也就被释放了
};
blk(); //并且一定要运行一次, 否则不能被释放
这样就使blk断开了与blockSelf的持有关系.
这么做好处是可以自己控制对self的持有时间.
在arc中, block修饰的变量会引用到,并且计算+1
在非arc中,block修饰的变量的引用计算是不变的。
- 总结
- 在MRC 时代,block 修饰,可以避免循环引用;ARC时代,block 修饰,同样会引起循环引用问题
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型
- __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型
- block对象可以在block中被重新赋值,weak不可以
- unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符。(__weak 会自动置为nil)