iOS进阶笔记(四) Block - 基础篇
前言:
Block的使用与C语言的函数指针比较类似。 为方便理解,我们先看看C语言函数指针的使用。
一、C语言函数指针
C 语言函数指针使用步骤拆解:
- 函数指针定义
returnType (*func_ptr)(parameterTypes)
- 指针赋值:将函数指针指向函数首地址
func_ptr = func_name;
- 指针函数调用:
func_ptr(argumentTypes);
具体示例如下:
int (*ptr)(int,int);
int func_name(int a, int b) {
return a+b;
}
int main(void) {
// 指针赋值:函数指针ptr指向了addFunc函数首地址
ptr = func_name;
// 函数指针调用
int result = ptr(1,2);
printf(“ %d”,result);// 3
return 0;
}
二、Block定义和使用
1. Block定义
Block,在其他语言中也称作Closure(闭包),它封装了一段代码块,在需要执行的时候调用。
Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。
Block定义经典配图
Block使用步骤拆解:
- block定义:
returnType (^blockName)(parameterTypes,…);
- block赋值:block的实现赋值给block变量:
blockName = ^returnType(argumentTyps) {...};
- block调用:
blockName(argumentTyps);
简单示例:
int (^myBlock)(int a, int b);
int main(void) {
// block赋值:将block的实现赋值给myBlock变量
myBlock = ^(int a, int b){
return a + b;
};
// block调用
int result = myBlock(1,2);
printf(“%d”,result);// 3
return 0;
}
2. Block的用法
Block的用法 | |
---|---|
作为局部变量 | returnType (^blockName)(parameterTypes) = ^returnType(argumentTyps) {...}; |
作为属性 | @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes); |
作为方法形参 | - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName; |
作为方法调用实参 | [someObject someMethodThatTakesABlock:^returnType (argumentTyps) {...}]; |
作为C函数的形参 | void SomeFunctionThatTakesABlock(returnType (^blockName)(parameterTypes)); |
typedef用法 | typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(argumentTyps) {...}; |
Tips: 注意文中的函数(方法)参数表示,parameter表示形参,argument表示实参。 |
3. __block使用
通过__block
可以捕获auto变量,再次修改auto变量的值,block内部会同时被修改。
__block详细分析请查看《iOS进阶笔记》Block原理篇中__block捕获变量原理分析小节。
int a = 10;
__block int b = 20;
void (^myBlock)(void) = ^ {
NSLog(@“a:%d b:%d”,a,b);
};
a = 11;
b = 21;
myBlock();// 结果 a=10,b= 21
4. 通过block实现链式编程
示例如下:
- Cat类结构
// Cat.h
@interface Cat : NSObject
- (Cat *)sleep:(int)hours;
- (Cat *)eat:(NSString *)foodName;
- (Cat *(^)(int))sleep;
- (Cat *(^)(NSString *))eat;
@end
// Cat.m
@implementation Cat
- (Cat *)sleep:(int)hours
{
NSLog(@"sleep %d",hours);
return self;
}
- (Cat *)eat:(NSString *)foodName
{
NSLog(@"eat %@",foodName);
return self;
}
- (Cat *(^)(int))sleep{
return ^(int hours){
NSLog(@"sleep %d hours",hours);
[self like];
return self;
};
}
- (Cat *(^)(NSString *))eat{
return ^(NSString *foodName){
NSLog(@"eat %@",foodName);
return self;
};
}
@end
- Cat对象使用
Cat *cat = [[Cat alloc] init];
// 普通链式
[[cat sleep:10] eat:@"fish"];
// block链式
cat.sleep(3).eat(@"fish");
链式的本质就是利用返回值为当前方法调用者,可再持续调用方法的特性。
三、Block循环引用问题
block 循环引用一般分两种,一种是block和对象相互直接持有,另一种是间接持有。
1、相互直接持有举例
self指针指向viewController
控制器对象内存,myBlock
指针又指向了block
对象内存;
viewController
的内存又持有myBlock
对象的指针,block
又持有self
指针。加之二者都是强指针(strong
),这样形成了相互持有谁也无法释放,因此造成相互直接持有的循环引用。这种情况下,编译器也会有相应的提示。
2、间接持有举例
self指针指向viewController
控制器对象内存,person
指针指向了Person对象内存;myBlock
指针又指向了block对象内存;
viewController对象内存持有person指针,person对象又持有blcok指针,而block对象又持有self指针;加之三者都是强指针,造成三者间循环持有,谁都无法释放,因此造成间接持有循环引用。
3、打破循环引用措施
对于循环引用,只要将中间的强引用(strong
)关系变为弱引用(weak
)关系即可。
- 相互直接持有解决办法
相关代码段:
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf);
};
相当于将self指针由strong变为weak,切断了循环引用。这样即使weak指针指向viewController,当viewController没有其它对想引用时,引用计数依然会清零,不影响viewController的释放。
- 间接相互持有解决办法
相关代码段:
self.person = [[Person alloc] init];
__weak typeof(self) weakSelf = self;
self.person.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf);
};
}
相当于将self指针由strong变为weak,切断了循环引用。这样即使weak指针指向 viewController,当viewController没有其它对想引用时,引用计数依然会清零,不影响viewController的释放。viewController在释放之前,其持有的person对象会被释放,person持有的block也会被释放,不会造成僵尸对象的存在。
Tips:block内部使用 __strong typeof(weakSelf) strongSelf = weakSelf 目的是,在block内部可能还存在self调用其它函数的情况,若只使用weakSelf,可能在没调用该函数self已经被释放了。因此使用__strong声明一个strongSelf的局部强指针,确保在block执行完之前self指针依然存在。
四、Block的内存语义
Block本身是像对象。
在MRC下,一样可以retain,和release。block的retain行为默认是用copy的行为实现的(编译器的优化)。block在创建的时候,它的内存是分配在栈上的,而不是在堆上。而栈区的特点就是创建的对象在离开当前作用域随时可能被销毁。一旦被销毁,后续再次调用就会导致程序异常。在对block进行copy后,就在堆区生成一个block对象的副本,并可由block指针所引用。因此在使用block时,需要将其属性声明为copy。
在ARC下,使用strong也是可以的,但是为了显式表示block的内存语义,建议使用copy。Apple文档《Working with Blocks》 中 Objects Use Properties to Keep Track of Blocks 小节说的也很清楚。
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.
以上(完)
----------End------------
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!