1.block类型-存储代码块的类型
在异步编程时常需要进行函数回调,在C#中会用匿名委托或者lambda表达式讲一个操作作为参数进行传递.
ObjC中是使用对于闭包的实现,在块状中我们可以持有或引用局部变量. 同时利用Block可以将一个操作作为参数进行传递;
blcok用法:
- 定义:返回值类型 ( ^变量名 ) ( 形参类型 );
- 赋值:变量名=^(形参){
代码块+形参变量
}; - 使用:变量(实参);
例:
int (^myBlcok)(int ,int)=^(int m,int n){
return m+n;
}; //无参数时大括号前()可省略
myBlock(10,5); //调用块,省略了接受块返回值;
总结:经过简单了解C与OC;发现从最小的一个变量到表达式再到一个函数,其实只起两点作用: 值(返回值) 与 功能(行为,方法,作用).
所以说一行代码,按它是使用了值 还是 功能来解读比较容易理解.
Block做使用场景:
- 如果回调方法比较少,1~2,最好不要超过3个,这个时候使用block比较合适
- 如果回调方法非常多,同时又不用每一个方法都必须实现,这个时候用delegate会比较方便!
block传值的循环引用问题:
只有当block直接或间接的被self持有时,在block使用self时才需要替换为weak self。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
});
typedef block格式
类似函数指针,直接在定义格式之前加 typedef关键字,之后变量名就是类型的别名了.
typedef viod (^别名)(形参);
一般以后需要使用block作为函数方法的参数时,为方便最好用别名.而在block作用返回值时,一定需要别名,因为编译器不能识别做此时类型做何种解释.
延伸:经过测试,block在编译时按代码顺序,而运行时按调用顺序(变量作用域)
使用例子:
KCButton.h
#import
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);
@interface KCButton : NSObject
#pragma mark - 属性
@property (nonatomic,copy) KCButtonClick onClick;
#pragma mark 点击方法
-(void)click;
@end
KCButton.m
#import "KCButton.h"
@implementation KCButton
-(void)click{
NSLog(@"Invoke KCButton's click method.");
if (_onClick) {
_onClick(self);
}
}
@end
main.m
KCButton *button=[[KCButton alloc]init];
button.onClick=^(KCButton *btn){
NSLog(@"Invoke onClick method.The button is:%@.",btn);
};
[button click];
/*结果:
Invoke KCButton's click method.
Invoke onClick method.The button is:.
*/
block访问外部变量
- block内部可以访问外部局部变量,但是此时是const copy方式,地址不同,相当于值传递,只读的.如果外部定义时加前缀__block时,内部可改变外部局变值.
- block内部如果创建了和外部同名的变量,会屏蔽外部作用域.此时内部的变量也存在栈区;
原因:block本质是代码块,ARC下创建的时候在堆区,此时代码只是单纯储存,没有功能;当调用的时候,相当于代码增加到main中,这样代码块中创建的变量就跟正常的一样; (block调用完成内部变量即释放,而堆区的只在释放block时一起释放). - 如果是静态变量(static修饰局变,生命周期延长,存储在数据区(同初始化的全局))和全局变量.地址传递.此时block存储在全局区.
- 常量字符串@"abc",加__block会引用常量变量(如:a变量,a = @"abc",内部可以任意修改a 指向的内容)的地址。不加block就是@"abc"本身地址,不可变;
三种类型block
根据block在内存中的位置
"NSGlobaBlock"类似函数,存于代码区--全局block
"NNStackBlock"栈区,函数返回后的Block--栈
"NSMallocBlock"堆block--堆
- block内没有使用外部变量或是只使用了全局/静态变量时.存于全局代码区,为全局block;---(ARC和MRC下一致)
- 当使用外部变量时
- MRC下,block代码存于栈区;如果此外部变量A存于栈区,那么A会被copy到block分配的栈区;如果A是存于堆区,那么A在block块内与快外相同.
- ARC下,block代码存于堆区.如果此外部变量A存于栈区,那么A会被copy到block分配的堆区;如果A是存于堆区,那么A在block块内与快外相同.
- 如果需要修改外部变量,需要在变量前面声明__Block;
当使用下划线Block修饰外部变量时:- MRC下,无论变量A存于栈还是堆区,A在block块内与快外相同;
- ARC下,如果此外部变量A存于栈区,那么A会被转移而不是复制到堆区;如果A是存于堆区,那么A在block块内与快外相同.
面试题:block的@property参数(内存管理参数)为什么要用copy:如果不用copy,此时不论ARC还是MRC都是栈Bolck,栈block会提前释放,导致无法继续使用;可以copy到堆区手动管理内存.(而字符串copy是防止字符串如果是非常量的,外部可变,造成非预估的结果;)
block在MRC下得内存隐患(NNStacKBlock)
Block_copy将block及内部变量拷贝到堆区.
使用完毕用Blok_release(block变量)释放此堆区空间;
block使用技巧
- block结构快速显示:inlineBlock...(也可右下角自定义快速显示其他格式)
- Block作为方法参数时,最好把参数列表部分加上,这样后面调用方法时,会自动有格式;
- 做方法参数时,需要加上返回值类型;
- 做返回值时,先定义别名,最后别忘记执行返回值;
- 方法中,void(^)()表示block类型同int,做参和返回值;做实例变量
@property (nonatomic, copy) void(^变量名)() - get点语法获取block类型实例变量时,自动执行,后面需加();