追溯 解析 循环 横空出世 返回值 weak void 新的
在 iOS4.0 之后,block 横空出世,它本身封装了一段代码并将这段代码当做变量,通过 block() 的方式进行回调。
先来一段简单的代码看看:
- void (^myBlock)(int a) = ^(int a){
- NSLog(@"%zd",a);
- };
- NSLog(@"旭宝爱吃鱼");
- myBlock(999);
输出结果:
2016-05-03 11:27:18.571 block[5340:706252] 旭宝爱吃鱼 2016-05-03 11:27:18.571 block[5340:706252] 999
下面我们解析一下:
通过上面的简单介绍可以简单了解到 block 的结构那么下面便产生了四种格式的 block。
有返回值无参:
- int (^myBlock)() = ^(){
- return 999;
- };
有返回值有参:
- int (^myBlock)(int a) = ^(int a){
- NSLog(@"%zd",a);
- return a;
- };
无返回值无参:
- void (^myBlock)() = ^(){
- NSLog(@"旭宝爱吃鱼");
- };
无返回值有参:
- void (^myBlock)(int a) = ^(int a){
- NSLog(@"%zd",a);
- };
示例代码:
- int a = 10;
- void (^myBlock)() = ^(){
- NSLog(@"旭宝爱吃鱼");
- NSLog(@"%zd",a);
- };
- NSLog(@"旭宝爱吃鱼");
- myBlock();
运行结果:
2016-05-03 11:43:32.680 block[5406:713702] 旭宝爱吃鱼 2016-05-03 11:43:32.681 block[5406:713702] 旭宝爱吃鱼 2016-05-03 11:43:32.681 block[5406:713702] 10
通过示例代码不难发现我们可以获取局部变量,那么改变局部变量是否也会改变 block 内的值呢。
示例代码:
- int a = 10;
- void (^myBlock)() = ^(){
- NSLog(@"旭宝爱吃鱼");
- NSLog(@"%zd",a);
- };
- a = 20;
- NSLog(@"旭宝爱吃鱼");
- myBlock();
运行结果:
2016-05-03 11:47:07.669 block[5425:715861] 旭宝爱吃鱼 2016-05-03 11:47:07.670 block[5425:715861] 旭宝爱吃鱼 2016-05-03 11:47:07.670 block[5425:715861] 10
正如结果显示,block 内部的值是不会改变的,为什么呢????
示例代码:
- int a = 10;
- a = 20;
- void (^myBlock)() = ^(){
- NSLog(@"旭宝爱吃鱼");
- NSLog(@"%zd",a);
- };
- NSLog(@"旭宝爱吃鱼");
- myBlock();
运行结果:
2016-05-03 11:49:19.749 block[5450:717309] 旭宝爱吃鱼 2016-05-03 11:49:19.750 block[5450:717309] 旭宝爱吃鱼 2016-05-03 11:49:19.750 block[5450:717309] 20
情理之中的答案。之所以 block 内部的值不会改变是因为 block copy 了局部变量。
这个问题解决后尝试在 block 中改变局部变量。很不幸失败了,当我在 block 内改变外界的局部变量时,报错了。可视化我想改变外界的局部变量我该怎么办呢???
示例代码:
- __block int a = 10;
- void (^myBlock)() = ^(){
- NSLog(@"旭宝爱吃鱼");
- NSLog(@"%zd",a);
- a = 20;
- };
- NSLog(@"旭宝爱吃鱼");
- myBlock();
- NSLog(@"%zd",a);
运行结果:
2016-05-03 11:55:02.736 block[5490:721033] 旭宝爱吃鱼 2016-05-03 11:55:02.737 block[5490:721033] 旭宝爱吃鱼 2016-05-03 11:55:02.737 block[5490:721033] 102016-05-03 11:55:02.737 block[5490:721033] 20
我们只需要在局部变量前加__block 即可。
block 在 iOS 开发中被视作是对象,因此其生命周期会一直等到持有者的生命周期结束了才会结束。另一方面,由于 block 捕获变量的机制,使得持有 block 的对象也可能被 block 持有,从而形成循环引用,导致两者都不能被释放:
- @implementation CXObject
- {
- void (^_cycleReferenceBlock)(void);
- }
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- _cycleReferenceBlock = ^{
- NSLog(@"%@", self); //引发循环引用
- };
- }
- @end
遇到这种代码编译器只会告诉你存在警告,很多时候我们都是忽略警告的,这最后会导致内存泄露,两者都无法释放。跟普通变量存在 block 关键字一样的,系统提供给我们 weak 的关键字用来修饰对象变量,声明这是一个弱引用的对象,从而解决了循环引用的问题:
- __weak typeof(*&self) weakSelf = self;
- _cycleReferenceBlock = ^{
- NSLog(@"%@", weakSelf); //弱指针引用,不会造成循环引用
- };
在 block 出现之前,开发者实现回调基本都是通过代理的方式进行的。比如负责网络请求的原生类 NSURLConnection 类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的 NSURLSession 已经采用 block 的方式处理任务请求了。各种第三方网络请求框架也都在使用 block 进行回调处理。这种转变很大一部分原因在于 block 使用简单,逻辑清晰,灵活等原因。接下来我会完成一次网络请求,然后通过 block 进行回调处理。这些回调包括请求完成、下载进度
按照 returnValue(^blockName)(parameters) 的方式进行 block 的声明未免麻烦了些,我们可以通过关键字 typedef 来为 block 起类型名称,然后直接通过类型名进行 block 的创建:
- @interface CXDownloadManager: NSObject
- //block重命名
- typedef void(^CXDownloadHandler)(NSData * receiveData, NSError * error);
- typedef void(^CXDownloadProgressHandler)(CGFloat progress);
- - (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress;
- @end
- @implementation CXDownloadManager
- {
- CXDownloadProgressHandler _progress;
- }
- - (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress
- {
- //创建请求对象
- NSURLRequest * request = [self postRequestWithURL: URL params: parameters];
- NSURLSession * session = [NSURLSession sharedSession];
- //执行请求任务
- NSURLSessionDataTask * task = [session dataTaskWithRequest: request completionHandler: ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
- if (handler) {
- dispatch_async(dispatch_get_main_queue(), ^{
- handler(data, error);
- });
- }
- }];
- [task resume];
- }
- //进度协议方法
- - (void)URLSession:(NSURLSession *)session
- downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didWriteData:(int64_t)bytesWritten // 每次写入的data字节数
- totalBytesWritten:(int64_t)totalBytesWritten // 当前一共写入的data字节数
- totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data字节数
- {
- double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
- if (_progress) { _progress(downloadProgress); }
- }
- @end
上面通过封装 NSURLSession 的请求,传入一个处理请求结果的 block 对象,就会自动将请求任务放到工作线程中执行实现,我们在网络请求逻辑的代码中调用如下:
- #define QQMUSICURL @"https://www.baidu.com/link?url=UTiLwaXdh_-UZG31tkXPU62Jtsg2mSbZgSPSR3ME3YwOBSe97Hw6U6DNceQ2Ln1vXnb2krx0ezIuziBIuL4fWNi3dZ02t2NdN6946XwN0-a&wd=&eqid=ce6864b50004af120000000656fe235f"
- [[CXDownloadManager alloc] downloadWithURL: QQMUSICURL parameters: nil handler ^(NSData * receiveData, NSError * error) {
- if (error) { NSLog(@"下载失败:%@", error) }
- else {
- //处理下载数据
- }
- } progress: ^(CGFloat progress) {
- NSLog(@"下载进度%lu%%", progress*100);
- }];
block 捕获变量、代码传递、代码内联等特性赋予了它多于代理机制的功能和灵活性,尽管它也存在循环引用、不易调试追溯等缺陷,但无可置疑它的优点深受码农们的喜爱。如何更加灵活的使用 block 需要我们对它不断的使用、探究了解才能完成
iOS block 从零开始
来源: http://www.bubuko.com/infodetail-2127106.html