GCD简介以及block
2016-04-05 15:11
441 查看
1.GCD简介以及block
GCD:Grand Central Dispatch或者GCD,是一套low level API,提供了一种新的方法来进行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分。使用GCD比使用NSOpertionQueue方便;
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。该方法在Mac OS X 10.6中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,它看起来象就其它语言的闭包(Closure)一样,但苹果把它叫做blocks。
block:块是C语言的一种扩展。先给个典型的代码:
^(void){
NSLog(@"Program is funning\n");
} ;
由上例,典型的block指的是以插入“^”为开头为标识的代码块,^后面跟的(void)表示块所需要的参数列表;
当然,我们可以定义一个变量来表示这个block,就像是函数指针的感觉(有两种方法):
(1)方法1:如下例:
void (^printMessage) (void)=
^(void){
NSLog(@"Program is funning\n");
};
等号左边表示的是printMessage指向一个没有参数和返回值的块指针,第一个void是返回值(和C语言函数语法一致),第二个void指的是传入的参数(类比于函数的形参);
执行一个变量引用的块,与函数调用方法一致:
printMessage();
(2)方法2:如下例:(更加常用的方法)
dispatch_block_t block = ^{
//do something
};
调用方法一致:
block();
1
关于block变量:
(1)对于局部自动变量:在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
int base = 100;
BlkSum sum = ^ long (int a, int b) {
// base++; 编译错误,只读
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 这里输出是103,而不是3
注意:,可以从上面看到,这是block只会从定义该block之前取值,对于base=0这条语句是忽略的;
(2)static变量、全局变量。如果把上个例子的base改成全局的、或static。Block就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。
static int base = 100;
BlkSum sum = ^ long (int a, int b) {
base++;
return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 这里输出是3,而不是103
printf("%d\n", base);
输出结果是0 4 1,表明Block外部对base的更新会影响Block中的base的取值,同样Block对base的更新也会影响Block外部的base值。
(3)对于block型的变量:被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
也就是说,经过__block修饰的变量是不会随着对象的重新生成而改变地址,而是类似于静态全局变量一样,从程序开始到结束,仅有一个变量地址,这一点在编程时经常出错;
(4)block会对其内部的变量进行强引用,这是可能造成内存循环引用的问题。
注意:block在GCD中是必需的,所以要重点掌握。
2.GCD语法
1.三大概念:(非常重要)
(1)队列和任务:
队列:用来存放任务;
任务:用来执行什么操作,简单理解可以理解为自定义的一个函数;
(2)同步和异步:
同步:只能在当前线程中执行任务,不具备开启新线程的能力;
异步:可以在新的线程中执行任务,具备开启新线程的能力;
说明:两者最大的区别就是开不开线程,可以这样理解,对于一件事情,同步是会只在一个线程一直执行下去,而对于异步,则会一需要执行该任务,开一条新的线程,把这个任务交给该线程执行下去,立即返回原来的线程,这样子达到了异步的效果;
(3)并发和串行队列:
并发:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步(dispatch_async)函数下才有效;
串行:让串行队列内任务一个接着一个地执行(串行队列内一个任务执行完毕后,再执行下一个任务);但对于每个串行队列,是并发进行的
这里写图片描述
2.使用方法:
(1)第一步,确定自己要执行的任务,也就是编写好任务对应的函数;
(2)第二步,将写好的任务添加到队列中,GCD会自动将队列中得任务取出,放到对应的线程中执行,注意,这里系统是自动放到线程中执行;
3.实际使用方法(示例代码):
(1)第一步,先定义一个队列:
*全局的并发队列 : 可以让任务并发执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1
注: GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建,每次一运行程序就存在的,我们要做的只是使用dispatch_get_global_queue函数获得全局的并发队列;
使用格式:
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 队列的优先级
unsigned long flags); // 此参数暂时无用,用0即可
```
关于priority,系统提供了4种优先级:
```
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
关于DISPATCH_QUEUE_PRIORITY_DEFAULT参数的理解:一般都选这个参数就可以了;
*自己创建的串行队列 : 让任务一个接着一个执行
dispatch_queue_t queue = dispatch_queue_create("cn.heima.queue", NULL);
1
*主队列 : 让任务在主线程执行,也就是界面所在的线程
dispatch_queue_t queue = dispatch_get_main_queue();//这个就是主线程
1
(2)第二步,将队列放入程序执行,如下:
1> 同步执行 : 不具备开启新线程的能力
dispatch_sync...
注意
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
2> 异步执行 : 具备开启新线程的能力,但不代表每次都开新的线程
dispatch_async...
例子:
dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
printf("this is a block!\n");
});
上面这个例子就是简单地一个GCD的使用代码,先定义一个queue,可以如上选择全局队列,也可以选择其他队列,然后根据是同步还是异步选择dispatch_async或者dispatch_sync将队列放在该函数执行,一运行dispatch_async或者dispatch_sync,系统自动管理线程去运行该函数。
GCD的用法很多,更加常见的搭配使用方法如下:
dispatch_async + 全局并发队列
dispatch_async + 自己创建的串行队列
同时,线程与主线程之间的通信也是常见的GCD用处之一,一般使用格式如下(经典用法):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});
这也就是不阻塞主线程常用的方法,注意,只有异步才能达到这个效果(注意不要弄错)。
关于一次性执行代码
//在当前线程执行任务,能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"once");
NSLog(@"%@",[NSThread currentThread]);
});
关于延时执行
//方法1
//在当前线程睡眠3秒,如果在主线程,则会卡住界面;不推荐使用
// [NSThread sleepForTimeInterval:3];
//方法2
//一旦定制好延时延误后,不会卡住当前线程
//谁调用这条函数,n秒后回到该线程继续执行run方法
// [self performSelector:@selector(run ) withObject:nil afterDelay:3];
//方法3(放在主线程延时)
//GCD
//放在主线程延时,传入的是dispatch_get_main_queue()主队列
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"task");
// NSLog(@"run %@",[NSThread currentThread]);
//
// });
//方法3(放在主线程延时)
//不放在主线程延时,会自动开线程,3秒后在新线程执行任务函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
NSLog(@"task");
NSLog(@"run %@",[NSThread currentThread]);
});
关于队列组:
有这么1种需求
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
})
参考例子:
同时从网站上面下载两张图片,将下载过程放在队列组,两张都下载完成后执行合并图片操作,最后在主线程刷新图片:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//创建一个队列组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block UIImage * image1 = nil;
__block UIImage * image2 = nil;
dispatch_group_async(group, queue, ^{
//执行一个耗时的异步操作
//下载第一张
NSURL *url1 = [NSURL URLWithString:@"http://g.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee460de6182ff5e0fe99257e80.jpg"];
NSData *data1 = [NSData dataWithContentsOfURL:url1];
image1 = [UIImage imageWithData:data1];
});
dispatch_group_async(group, queue, ^{
//执行一个耗时的异步操作
//下载第二张
NSURL *url2 = [NSURL URLWithString:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
NSData *data2 = [NSData dataWithContentsOfURL:url2];
image2 = [UIImage imageWithData:data2];
});
//等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_notify(group, queue, ^{
//合并图片
//开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(image1.size, NO, 0.0);
//绘制第一张图片
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
//绘制第二章图片
[image2 drawInRect:CGRectMake(0, 0, image2.size.width*0.5, image2.size.height*0.5)];
//得到上下文中的图片
UIImage *fullImgae = UIGraphicsGetImageFromCurrentImageContext();
//结束上下文
UIGraphicsEndImageContext();
//回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.iamgeView.image = fullImgae;
});
});
}
5
在这里推荐一下:
1.http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/代码参考了这个网址
2.我在github共享的多线程的总结资料
3.http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html(这个比较详细)
4.http://mobile.51cto.com/iphone-402967.htm(这篇比较通俗
GCD:Grand Central Dispatch或者GCD,是一套low level API,提供了一种新的方法来进行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分。使用GCD比使用NSOpertionQueue方便;
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。该方法在Mac OS X 10.6中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,它看起来象就其它语言的闭包(Closure)一样,但苹果把它叫做blocks。
block:块是C语言的一种扩展。先给个典型的代码:
^(void){
NSLog(@"Program is funning\n");
} ;
由上例,典型的block指的是以插入“^”为开头为标识的代码块,^后面跟的(void)表示块所需要的参数列表;
当然,我们可以定义一个变量来表示这个block,就像是函数指针的感觉(有两种方法):
(1)方法1:如下例:
void (^printMessage) (void)=
^(void){
NSLog(@"Program is funning\n");
};
等号左边表示的是printMessage指向一个没有参数和返回值的块指针,第一个void是返回值(和C语言函数语法一致),第二个void指的是传入的参数(类比于函数的形参);
执行一个变量引用的块,与函数调用方法一致:
printMessage();
(2)方法2:如下例:(更加常用的方法)
dispatch_block_t block = ^{
//do something
};
调用方法一致:
block();
1
关于block变量:
(1)对于局部自动变量:在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
int base = 100;
BlkSum sum = ^ long (int a, int b) {
// base++; 编译错误,只读
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 这里输出是103,而不是3
注意:,可以从上面看到,这是block只会从定义该block之前取值,对于base=0这条语句是忽略的;
(2)static变量、全局变量。如果把上个例子的base改成全局的、或static。Block就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。
static int base = 100;
BlkSum sum = ^ long (int a, int b) {
base++;
return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 这里输出是3,而不是103
printf("%d\n", base);
输出结果是0 4 1,表明Block外部对base的更新会影响Block中的base的取值,同样Block对base的更新也会影响Block外部的base值。
(3)对于block型的变量:被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
也就是说,经过__block修饰的变量是不会随着对象的重新生成而改变地址,而是类似于静态全局变量一样,从程序开始到结束,仅有一个变量地址,这一点在编程时经常出错;
(4)block会对其内部的变量进行强引用,这是可能造成内存循环引用的问题。
注意:block在GCD中是必需的,所以要重点掌握。
2.GCD语法
1.三大概念:(非常重要)
(1)队列和任务:
队列:用来存放任务;
任务:用来执行什么操作,简单理解可以理解为自定义的一个函数;
(2)同步和异步:
同步:只能在当前线程中执行任务,不具备开启新线程的能力;
异步:可以在新的线程中执行任务,具备开启新线程的能力;
说明:两者最大的区别就是开不开线程,可以这样理解,对于一件事情,同步是会只在一个线程一直执行下去,而对于异步,则会一需要执行该任务,开一条新的线程,把这个任务交给该线程执行下去,立即返回原来的线程,这样子达到了异步的效果;
(3)并发和串行队列:
并发:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步(dispatch_async)函数下才有效;
串行:让串行队列内任务一个接着一个地执行(串行队列内一个任务执行完毕后,再执行下一个任务);但对于每个串行队列,是并发进行的
这里写图片描述
2.使用方法:
(1)第一步,确定自己要执行的任务,也就是编写好任务对应的函数;
(2)第二步,将写好的任务添加到队列中,GCD会自动将队列中得任务取出,放到对应的线程中执行,注意,这里系统是自动放到线程中执行;
3.实际使用方法(示例代码):
(1)第一步,先定义一个队列:
*全局的并发队列 : 可以让任务并发执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1
注: GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建,每次一运行程序就存在的,我们要做的只是使用dispatch_get_global_queue函数获得全局的并发队列;
使用格式:
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 队列的优先级
unsigned long flags); // 此参数暂时无用,用0即可
```
关于priority,系统提供了4种优先级:
```
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
关于DISPATCH_QUEUE_PRIORITY_DEFAULT参数的理解:一般都选这个参数就可以了;
*自己创建的串行队列 : 让任务一个接着一个执行
dispatch_queue_t queue = dispatch_queue_create("cn.heima.queue", NULL);
1
*主队列 : 让任务在主线程执行,也就是界面所在的线程
dispatch_queue_t queue = dispatch_get_main_queue();//这个就是主线程
1
(2)第二步,将队列放入程序执行,如下:
1> 同步执行 : 不具备开启新线程的能力
dispatch_sync...
注意
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
2> 异步执行 : 具备开启新线程的能力,但不代表每次都开新的线程
dispatch_async...
例子:
dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
printf("this is a block!\n");
});
上面这个例子就是简单地一个GCD的使用代码,先定义一个queue,可以如上选择全局队列,也可以选择其他队列,然后根据是同步还是异步选择dispatch_async或者dispatch_sync将队列放在该函数执行,一运行dispatch_async或者dispatch_sync,系统自动管理线程去运行该函数。
GCD的用法很多,更加常见的搭配使用方法如下:
dispatch_async + 全局并发队列
dispatch_async + 自己创建的串行队列
同时,线程与主线程之间的通信也是常见的GCD用处之一,一般使用格式如下(经典用法):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});
这也就是不阻塞主线程常用的方法,注意,只有异步才能达到这个效果(注意不要弄错)。
关于一次性执行代码
//在当前线程执行任务,能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"once");
NSLog(@"%@",[NSThread currentThread]);
});
关于延时执行
//方法1
//在当前线程睡眠3秒,如果在主线程,则会卡住界面;不推荐使用
// [NSThread sleepForTimeInterval:3];
//方法2
//一旦定制好延时延误后,不会卡住当前线程
//谁调用这条函数,n秒后回到该线程继续执行run方法
// [self performSelector:@selector(run ) withObject:nil afterDelay:3];
//方法3(放在主线程延时)
//GCD
//放在主线程延时,传入的是dispatch_get_main_queue()主队列
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"task");
// NSLog(@"run %@",[NSThread currentThread]);
//
// });
//方法3(放在主线程延时)
//不放在主线程延时,会自动开线程,3秒后在新线程执行任务函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
NSLog(@"task");
NSLog(@"run %@",[NSThread currentThread]);
});
关于队列组:
有这么1种需求
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
})
参考例子:
同时从网站上面下载两张图片,将下载过程放在队列组,两张都下载完成后执行合并图片操作,最后在主线程刷新图片:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//创建一个队列组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block UIImage * image1 = nil;
__block UIImage * image2 = nil;
dispatch_group_async(group, queue, ^{
//执行一个耗时的异步操作
//下载第一张
NSURL *url1 = [NSURL URLWithString:@"http://g.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee460de6182ff5e0fe99257e80.jpg"];
NSData *data1 = [NSData dataWithContentsOfURL:url1];
image1 = [UIImage imageWithData:data1];
});
dispatch_group_async(group, queue, ^{
//执行一个耗时的异步操作
//下载第二张
NSURL *url2 = [NSURL URLWithString:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
NSData *data2 = [NSData dataWithContentsOfURL:url2];
image2 = [UIImage imageWithData:data2];
});
//等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_notify(group, queue, ^{
//合并图片
//开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(image1.size, NO, 0.0);
//绘制第一张图片
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
//绘制第二章图片
[image2 drawInRect:CGRectMake(0, 0, image2.size.width*0.5, image2.size.height*0.5)];
//得到上下文中的图片
UIImage *fullImgae = UIGraphicsGetImageFromCurrentImageContext();
//结束上下文
UIGraphicsEndImageContext();
//回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.iamgeView.image = fullImgae;
});
});
}
5
在这里推荐一下:
1.http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/代码参考了这个网址
2.我在github共享的多线程的总结资料
3.http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html(这个比较详细)
4.http://mobile.51cto.com/iphone-402967.htm(这篇比较通俗
相关文章推荐
- Ruby中Block和迭代器的使用讲解
- Ruby中使用Block、Proc、lambda实现闭包
- Ruby中的block、proc、lambda区别总结
- 如何消除inline-block属性带来的标签间间隙
- 全面解析Objective-C中的block代码块的使用
- 详解iOS多线程GCD的使用
- 详解IOS中GCD的使用
- 详解iOS中多线程app开发的GCD队列的使用
- block 实现原理详解(一)
- iOS页面传值总结
- ios高效开发二--ARC跟block那点事
- EOP / Office 365: Block or Allow IP Address in Connection Filtering
- objective-c block 讲解
- IOS block的学习
- gcd
- OC多线程
- 多线程应该知道的那几件事 GCD NSThread NSOperation
- 多线程编程4 - GCD
- 多线程学习资源
- block && Grand Central Dispatch