本章的内容是比较麻烦复杂的一章,牵扯到了GCD和Block,在之前也有学习过,本章作以深入的了解。多线程问题是开发应用程序的时候最让人头疼的问题,尤其是线程阻塞,在更新了Mac之后我经常的遇到了彩虹小球的问题,当然在开发的时候还没有出现过线程阻塞问题。
块可以实现闭包,这个特性是作为扩展引入的,它也是基于C语言特性的技术,包括在C C++,OC,OC++代码使用它。
块与函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围内的东西。块用“^”符号来表示,后边跟着一对花括号,括号里面是块的实现代码。
^{
block implementation
}
块也是一个值,它的语法和函数类似,语法结构如下:
return_type (^block_name)(parameters)
具体实例,这是定义一个名字为addBlock的块变量,可以类似于函数去使用它
#import <Foundation/Foundation.h>
NSString* (^addBlock)(NSString *a, NSString* b) = ^(NSString *a, NSString* b) {
return [NSString stringWithFormat:@"%@%@", a, b];
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSString* new = addBlock (@"3g", @"iOS");
NSLog(@"new = %@", new);
}
return 0;
}
块可以在它生命的范围里面所有变量都可以捕获。
#import <Foundation/Foundation.h>
NSString* c = @"ff";
NSString* (^addBlock)(NSString *a, NSString* b) = ^(NSString *a, NSString* b) {
return [NSString stringWithFormat:@"%@%@%@", a, b, c];
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSString* new = addBlock (@"3g", @"iOS");
NSLog(@"new = %@", new);
}
return 0;
}
在默认情况下我们捕获的C字符串是不可以修改的,不过声明变量的时候加入__block即可在块内修改。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block NSString* c = @"ff";
NSString* (^addBlock)(NSString *a, NSString* b) = ^(NSString *a, NSString* b) {
c = @"1234";
return [NSString stringWithFormat:@"%@%@%@", a, b, c];
};
@autoreleasepool {
// insert code here...
NSString* new = addBlock (@"3g", @"iOS");
NSLog(@"new = %@", new);
}
return 0;
}
如果块所捕获的变量是对象类型,那么就会自动保留它。系统在释放这个块的时候,也会将其一并释放。这就引出了一个于块有关的重要问题。块本身可视为对象。并且块本身也和其他对象一样,有引用计数。如果将块定义在OC类的实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量。块总能修改实例变量,所以在声明时无需加_block。不过,如果通过读取或写入操作捕获了实例变量,那么也会自动把self变量一并捕获了,因为实例变量是与self所指代的实例关联在一起的。也就是说,只要你在块中调用到了属性值,那么这个块就会捕获这个类本身也就是self。
OC对象都会占用某个内存区域,块本身也是对象,也存在内存区域和内存布局。
定义对象的时候是初始分配在栈上的,也就是有可能在使用之后内存被覆写,那样就是产生崩溃。
为了解决问题可以给块对象发送copy信息,这样子就会把块从栈复制到堆上,块也就成了带引用计数的对象了,在ARC下编译器会自动的合理的释放对象。
除了“栈块”和“堆块”之外,还有一类块叫做“全局块”。这种块不会捕捉任何状态(比如外围的变量),运行时也无须有状态来参与。而且全局块的copy属于空操作。可以把他认为是单例。
在使用单例模式封装网络请求的时候就是使用了全局块。
单例模式封装网络请求的代码就用到了这一条。
为了隐藏复杂的块类型,需要用到C语言的类型定义,typedef关键字。
typedef int (^EOCSomeBlock)(BOOL flag, int value);
如此以来与定义其他变量时一样,变量类型在左边,变量名在右边。
块也可以用来简便方法签名。
为用户界面编码时,一种常用的范式就是“异步执行任务”。这种范式的好处在于:处理用户界面的显示及触摸操作所用的线程,不会因为要执行I/O或网络通信这类耗时的任务而阻塞。这个线程通常称为主线程。异步执行任务的通常使用委托模式实现,也就协议传值。
将一个方法定义为块类型当作参数传给某个方法。
相比委托协议,块封装起来的时候可以在调用start方法时候以内联得形式定义completion handler,代码更加容易读懂。
委托模式还有缺点就是如果类分别使用多个获取器下载不同的数据,那么就得在delegate回调方法里根据传入参数切换。
使用块无需在回调方法里面切换,每个completion handler的逻辑都已经定义好了
有时需要在相关事件点执行回调操作,这种情况也可以使用handler块。就比如说是下载应用的进度条。我们为其添加一个观察者,并且在其值发生改变的时候我们调用其中的块。
此处传入的NSOperationQueue参数就表示触发通知时用来执行块代码的那个队列。这是个“队列操作”,而非“底层GCD队列” 这个在本章之后会学习到。
如果块所捕获的对象直接或间接的保留了块本身,那么就会出现一种相互引用的现象吗,也就是块中的保留环。