简单来讲,block就是将函数及其上下文封装起来的对象,从功能上能够把它看做是C++中的匿名函数,也可称之为块。html
Block类型写法:objective-c
返回值+(^块名)+(参数)= ^(参数){ 内容 }网络
以下所示:闭包
int (^myBlock)(int a, int b) = ^(int a, int b){ return a + b; };
Block本质上也是OC对象,因此每一个Block对象也有isa指针指向它们的类对象。根据Block类对象存储的内存空间的不一样可分为三种不一样的类,分别是:app
位于全局区的Block类:__NSGlobalBlock__异步
位于栈区的Block类:__NSStackBlock__函数
位于堆区的Block类:__NSMallocBlock__ui
void (^myBlock)(void)=^(void){ NSLog(@"global"); }; NSLog(@"%@",[myBlock class]); //输出: //__NSGlobalBlock__
栈区Block:当Block捕获了外部变量后,会被分配到栈区。可是在ARC环境下,系统会自动为生成的栈区Block进行copy操做,因此为了验证是不是在栈区,须要采用MRC环境,在main.m文件的编译选项设置为: -fno-objc-arc
后运行以下代码:url
NSString* flag=@"yes"; void (^myBlock)(void)=^(void){ NSLog(@"stack:%@",flag); }; NSLog(@"%@",[myBlock class]); //输出: //__NSStackBlock__
堆区Block:在MRC模式下,用copy后,会将栈区block复制到堆区。在ARC模式下,系统自动将初始化的Block复制到堆区。spa
//MRC环境下: NSString* flag=@"yes"; void (^myBlock)(void)=[^(void){ NSLog(@"stack:%@",flag); } copy]; NSLog(@"%@",[myBlock class]); //输出: //__NSMallocBlock__
官方的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 };
__NSGlobalBlock__
、 __NSStackBlock__
和 __NSMallocBlock__
,可是这里的底层isa实际上指向的是父类的结构体(C语言)即:_NSConcreteGlobalBlock
、 _NSConcreteStackBlock
和 _NSConcreteMallocBlock
结构体,但意义是同样的。copy
和dispose
方法用来拷贝和销毁捕获的变量。Block内部结构图(来自于Effective-OC):
在平常的开发中,使用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 } }];
当几个oc对象互相强引用成环时,就会致使对象永远都不会被释放,当这些对象的数量很大时,就会形成内存泄漏,从而致使整个系统crash的风险。
举个例子:
当A类对象强引用了B类对象,B类对象强引用了C类对象,而C对象又强引用了A类对象。假设它们都在一个代码段中。以下图所示:
由于a、b、c都被该代码段所强引用,因此retainCount初始化都为1,又由于它们互相强引用,因此在连成环的时候retainCount都变为了2。这时候在代码段中,不管是哪个对象先从代码段中释放,即retainCount--,都仍然还剩1。当整个代码段执行完后,三个类对象a、b、c的retainCount都从2减为了1,在整个系统中,再也没有其余影响因素会让它们的retainCount减小为0,这样就会致使这三个对象在运行中永不释放,从而形成内存泄漏。
在使用Block时也会很容易形成这个现象,当在网络异步的handler块中,咱们一般会将当前ViewController中的某个网络数据属性捕获到handler中,在网络链接成功后将其进行赋值,这样就至关于Block块间接地强引用了当前VC,而一般来讲,VC确定会强引用下载器,而下载器中的Block块通常也会作为其属性进行强引用。以下图所示:
为了解决强引用环的问题,能够经过将任意一个链接处断开便可。
断开1:基本不可能,在开发中在ViewController或者时ViewModel中都会将下载器做为属性而非临时变量,由于在调取过程当中会通常会根据当前下载状态来进行下一步操做。
断开2:
方法一:不将_downloadHandler做为属性,而是使用临时Block变量,一般这么作的状况是由于下载器类不须要屡次使用该block,对于复杂的下载器,这种策略很可贵以保证。
方法二:(推荐)在下载操做结束后调用的方法中令 self.downloadHandler = nil
,只要下载请求执行完毕,_downloadHandler属性就再也不强引用该block,就打破了强引用环。
断开3:
方法一:由于Block强引用了VC的data属性,实际上也就强引用了VC(self),因此咱们能够经过: __weak typeof(self) weakSelf=self
将当前VC,即self弱引用化,生成一个名为weakSelf的当前vc对象,而后在block中使用 weakSelf.data=_data
来进行调用。
方法二:方法一中大部分状况不会出现问题,可是当block块中有延时操做,而对_data的处理也在延时操做当中时,就会出现问题了,例如:
[self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //延迟2s获取data数据 weakSelf.data = onlineData; NSLog(@"%@",weakSelf.data); }); } else{ //handle error } }]; //假设成功从网络上获取到data //打印为空
这时候就会发现,不管是weakSelf仍是self的data属性都为空。这就是由于在block执行完后(延时函数还未执行完),weakSelf所在的弱引用表已经被除名了,虽然延时函数还在执行。这时候当2s事后,weakSelf已经变为了nil,对nil发送getter消息也不会报错,因此这里就会出现取值为空的状况。
为了解决这一问题,只须要在block内再将weakSelf在代码段内部强引用化(该强引用仅限于Block内部)。例如:
[self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success //将weakSelf强引用化生成该代码段的strong变量 __strong typeof(self) strongSelf=weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //延迟2s获取data数据 //这里使用strongSelf临时变量 strongSelf.data = onlineData; NSLog(@"%@",strongSelf.data); }); } else{ //handle error } }];
这里的strongSelf属于临时变量,会加到该代码段(Block内)的autoreleasepool当中,当该处代码段结束时会自动释放掉,因此也就不会出现强引用状况。
方法三:使用临时变量充当当前VC(self),以下:
__block XXXViewController* vc = self; //这里self的retainCount会+1 [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success vc.data = onlineData; //这里须要注意将该临时变量置为nil,即将retainCount从新减为1 vc=nil; } else{ //handle error } }];
这里须要注意在赋完值后必须将该临时变量从新置为nil,即将retainCount减1,不然仍会出现强引用的问题。
方法四:将当前self做为block参数传入,例如:
[self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded, XXXViewController* vc){ if(succeeded){ //download success vc.data = onlineData; } else{ //handle error } }];
这种状况通常不多出现,由于下载器一般做为第三方提供的API,一般参数不会有当前控制类。因此这种状况只能用在自定义block当中使用。