一 Block是什么?
我们使用^运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,后面要加;
- 声明Block变量
int (^block)(int) = NULL;
Block变量的语法
数据返回值类型 (^变量名)(参数列表) = NULL
- 赋值Block变量
block = ^(int m) { return m * m; };
- 使用Block变量
// 通过使用block变量,计算整型常量10的平方,并且打印在控制器输出 NSLog(@"10的平方是:%d",block(10));
示例代码:
// // ViewController.m // BlockDemo // // Created by lovestarfish on 15/11/16. // Copyright © 2015年 S&G. All rights reserved. // //不是block内部不能用self.也不是内部用了self就循环引用 #import "ViewController.h" //对block类型重命名,也就是把NSString*(^)(NSString*,NSString*)重命名 typedef NSString*(^YouBlock4)(NSString*,NSString*); @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self learnBlock]; } - (void)learnBlock { // ^代表就是block,代码块 int a; a = 10; //block很像一个函数,可以有参数,可以有返回值 //需要调用的时候,我们就去调用 //1. 无参数无返回值 void(^myBlock1)() = ^{ NSLog(@"无参数无返回值的block"); }; //调用block myBlock1(); //2. 有参数无返回值 void(^myBlock2)(int,int) = ^(int a,int b){ NSLog(@"%d",a + b); }; //调用block myBlock2(10,9); //3. 无参数有返回值 NSString* (^myBlock3)() = ^{ return @"无参数有返回值的block"; }; //调用block NSString *result = myBlock3(); NSLog(@"%@",result); //4. 有参数有返回值 NSString* (^myBlock4)(NSString *,NSString*) = ^(NSString *str1,NSString *str2){ return [str1 stringByAppendingString:str2]; }; //调用block NSString *result2 = myBlock4(@"hell",@"o"); NSLog(@"%@",result2); //5. block类型重命名 YouBlock4 bb = ^(NSString *str1,NSString *str2){ return [str1 stringByAppendingFormat:@"-----%@",str2]; }; NSLog(@"%@",bb(@"ni",@"hao")); } @end
通过以上代码可以得知,Block变量的使用步骤,类似于函数的步骤
- 首先都要声明(声明函数,声明block变量);
- 然后都要进行实现(实现函数,为block变量赋值实现过程);
- 最后都要进行调用才能实现具体功能
二 如何直接使用Block参数
- 数组排序
// 1.1 声明数组变量 NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@"5",@"2",@"3",@"9",@"7", nil]; // 1.2 直接用block进行数组升序排序 [mutableArray sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { // 将两个参数转换为字符串的对象 NSString *value1 = (NSString *)obj1; NSString *value2 = (NSString *)obj2; // value1与value2两个对象的比较结果直接返回 return [value1 compare:value2]; }]; // 1.3 打印可变数组变量 NSLog(@"%@",mutableArray);
- 简单的网络异步请求
// 2.1 声明网络地址对象 NSURL *url = [NSURL URLWithString:@"http://m.kuaidi100.com/query?type=quanfengkuaidi&postid=720140702702"]; // 2.2 根据网络地址对象,声明网络请求对象 NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 2.3 直接使用block变量完成链接成功后的数据返回功能 [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { // 将二进制数据使用utf8编码转换成相应类型的对象 NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; // 打印链接完成后的结果 NSLog(@"%@",dic); }];
三 深入理解Block语法
在本节,主要去介绍的是使用 __block 修饰的变量能够完成的作用
先看一个例子
// 1.声明一个局部整型变量 int intValue = 3; // 2.声明一个返回值为int,一个int参数的block变量 int (^block)(int) = ^(int m) { return m * intValue; }; // 3.调用block变量,5作为参数之后的结果 NSLog(@"block(5) = %d",block(5));
在本例中,intValue 变量称为block执行过程中的外部变量,在block执行过程中可以直接使用该外部变量.
再看一个例子
// 1.声明一个局部整型变量 int intValue = 3; // 2.声明一个返回值为int,一个int参数的block变量 int (^block)(int) = ^(int m) { intValue++; return m * intValue; }; // 3.调用block变量,5作为参数之后的结果 NSLog(@"block(5) = %d",block(5));
在这个例子中,编译器编译后发现有红色错误,错误如下图:
为什么会出现不能被赋值的错误提示呢?
- Block在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝
- 在block块内使用只读拷贝
为了避免上述错误,就要使用__block修饰符来修饰外部变量,用来通知编译器该外部变量 intValue 与 block 中的 intValue 指的是同一块内存地址,而不需要内存拷贝
如下例:
// 1.将intValue局部整型变量使用__block修饰符进行修饰 __block int intValue = 3; // 2.声明一个返回值为int,一个int参数的block变量 int (^block)(int) = ^(int m) { intValue++; return m * intValue; }; // 3.调用block变量,5作为参数之后的结果 NSLog(@"block(5) = %d",block(5));
四 使用Block要注意的内存问题
使用 weak-strong dance技术来避免循环引用
举例如下:
// // ViewController.m // BlockDemo // // Created by lovestarfish on 15/11/16. // Copyright © 2015年 S&G. All rights reserved. // //不是block内部不能用self.也不是内部用了self就循环引用 #import "ViewController.h" @interface ViewController () @end @implementation ViewController { id observer; } - (void)viewDidLoad { [super viewDidLoad]; // 添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象 observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"%@",self); }]; } /** * 当视图控制器对象销毁时,移除观察者 */ - (void)dealloc { if (observer) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } } @end
在上面代码中,我们向通知中心注册了一个观察者,然后在dealloc时解除该注册,一切看起来正常.但是里面有两个问题:
在消息通知 block 中引用了 self, 在这里 self 对象被 block 保留一次,而 observer 又 retain 该 block 的一份拷贝,通知中心又持有 observer . 因此只要 observer 对象还没有被解除注册, block 就会一直被通知中心持有,从而 self 就不会被释放, 其 dealloc 就不会被调用.而我们却又期望在dealloc中通过 removeObserver 来解除注册以消除通知中心对 observer/block 的保留次数
同时,observer 是在 self 所在类中定义赋值,因此是被 self retain 的,这样就形成了循环引用
// // ViewController.m // BlockDemo // // Created by lovestarfish on 15/11/16. // Copyright © 2015年 S&G. All rights reserved. // //不是block内部不能用self.也不是内部用了self就循环引用 #import "ViewController.h" @interface ViewController () @end @implementation ViewController { id observer; } - (void)viewDidLoad { [super viewDidLoad]; // 先声明一个weak弱对象 __weak ViewController *wSelf = self; // 添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象 observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { // 在block的执行过程中,使用强对象对弱对象进行引用 ViewController *bSelf = wSelf; if (bSelf) { NSLog(@"%@",bSelf); } }]; } /** * 当视图控制器对象销毁时,移除观察者 */ - (void)dealloc { if (observer) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } } @end
- 在 block 之前定义对 self 的一个弱引用 wSelf, 因为是弱引用,所以当 self 被释放时 wSelf 会变为 nil ;
- 在 block 中引用该弱引用,考虑到多线程情况,通过使用强引用 bSelf 来引用该弱引用,这时如果 self 不为 nil 就会 retain self,以防止在后面的使用过程中self 被释放
- 在之后的 block 块中使用该强引用 bSelf, 注意在使用前要对 bSelf 进行了 nil 检测,因为多线程环境下在用弱引用 wSelf 对强引用 bSelf 赋值时,弱引用 wSelf 可能已经为 nil 了
通过这种 weak-strong 手法,block就不会持有self 的引用,从而打破了循环引用