简单来说,block就是将函数及其上下文封装起来的对象,从功能上可以把它看作是C++中的匿名函数,也可称之为块。
Block类型写法:
返回值+(^块名)+(参数)= ^(参数){ 内容 }
如下所示:
int (^myBlock)(int a, int b) = ^(int a, int b){ return a + b; }; Block结构 Block存储区域Block本质上也是OC对象,所以每个Block对象也有isa指针指向它们的类对象。根据Block类对象存储的内存空间的不同可分为三种不同的类,分别是:
位于全局区的Block类:__NSGlobalBlock__
位于栈区的Block类:__NSStackBlock__
位于堆区的Block类:__NSMallocBlock__
全局区Block:当Block不捕获外部变量时,会被编译器分配到全局区。因为无外部变量,所以运行时不会在Block内部进行copy或dispose操作,为了削减开销,所以在编译时就确定了大小,即存储在全局区。如下:
void (^myBlock)(void)=^(void){ NSLog(@"global"); }; NSLog(@"%@",[myBlock class]); //输出: //__NSGlobalBlock__
栈区Block:当Block捕获了外部变量后,会被分配到栈区。但是在ARC环境下,系统会自动为生成的栈区Block进行copy操作,所以为了验证是否是在栈区,需要采用MRC环境,在main.m文件的编译选项设置为: -fno-objc-arc后运行如下代码:
NSString* flag=@"yes"; void (^myBlock)(void)=^(void){ NSLog(@"stack:%@",flag); }; NSLog(@"%@",[myBlock class]); //输出: //__NSStackBlock__
堆区Block:在MRC模式下,用copy后,会将栈区block复制到堆区。在ARC模式下,系统自动将初始化的Block复制到堆区。
//MRC环境下: NSString* flag=@"yes"; void (^myBlock)(void)=[^(void){ NSLog(@"stack:%@",flag); } copy]; NSLog(@"%@",[myBlock class]); //输出: //__NSMallocBlock__ Block内部结构官方的Block定义在 Block_private.h中,具体的源码:Block_private.h
#define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE void (*copy)(void *dst, const void *src); void (*dispose)(const void *); }; #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT }; //Block结构 struct Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 *descriptor; // imported variables };isa指针:指向类对象的指针,即根据不同分区指向: __NSGlobalBlock__、 __NSStackBlock__和 __NSMallocBlock__,但是这里的底层isa实际上指向的是父类的结构体(C语言)即:_NSConcreteGlobalBlock、 _NSConcreteStackBlock和 _NSConcreteMallocBlock结构体,但意义是一样的。
flags:类型为枚举,主要用来保存Block的状态信息。
reserved:为之后开发准备的保留信息,暂时无用。
invoke:函数指针,指向的是实际的功能运行函数。在invoke函数的参数中还包含了Block结构体本身,这么做的目的是在执行时,可以从内存中获取block中捕获的变量。
descriptor:主要存储Block的附加信息,其中包括占址大小、签名等。默认指向Block_descriptor_1结构体,当Block被copy到堆上时,则会添加Block_descriptor_2和Block_descriptor_3,新增copy和dispose方法用来拷贝和销毁捕获的变量。
Block内部结构图(来自于Effective-OC):
Block作用在日常的开发中,使用Block的主要用处在以下两个方面:
作为回调的方式之一,对比于代理模式,Block可将将分散的代码块集中写在一处编写。因为有捕获变量的机制,所以可以很轻松的访问上下文,并且Block的代码是内联的,运行效率会更高。
正是因为有了以上的优势,所以在编写异步代码,作为异步处理回调时,在封装时往往会采用handler块的方式来编写相关代码。
在编写handler块时有两种策略,一种是在一个方法中提供提供两个Block块分别处理CompletionHandler和errorHandler,另外一种是只提供一个Block块,在Block块中提供error参数,用户自己来对error值进行判断。一般我们更倾向于后者的方式,因为这样处理数据会更加灵活
两种Handler风格如下:
Downloader *myDownloader = [[Downloader alloc] initWithURL:url]; [myDownloader downloadWithCompletionHandler:^(NSData *onlineData){ //download success } failureHandler:^(NSError *error){ //handle error }]; Downloader *myDownloader = [[Downloader alloc] initWithURL:url]; [myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success } else{ //handle error } }]; Block内存泄漏当几个oc对象互相强引用成环时,就会导致对象永远都不会被释放,当这些对象的数量很大时,就会造成内存泄漏,从而导致整个系统crash的风险。
举个例子:
当A类对象强引用了B类对象,B类对象强引用了C类对象,而C对象又强引用了A类对象。假设它们都在一个代码段中。如下图所示: