IOS多线程 总结 -------------核心代码(GCD)
//NSObject
//在子线程中执行代码
// 参数1: 执行的方法 (最多有一个参数,没有返回值)
//参数2: 传递给方法的参数
[self performSelectorInBackground:@selector(cycling:) withObject:@"obj1"];
// 回到主线程更新页面
[self performSelectorOnMainThread:@selector(updateUI:) withObject:nil waitUntilDone:YES];
/1, NSThread
// 进入子线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(cycling:) object:@"thread"];
[thread start]; // 启动线程执行
[thread release];
// 直接启动
[NSThread detachNewThreadSelector:@selector(cycling:) toTarget:self withObject:@"thread2"];
//2, NSOperation
// 通常使用NSOperation下的两个子类:NSInvocationOperation、NSBlockOperation
// 1),NSInvocationOperation
NSInvocationOperation *invocationOperation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(cycling:) object:@"invocationOperation"] autorelease];
// [invocationOperation start]; // 启动任务
//2) NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10000; i++) {
NSLog(@"blockOperation: %i", i);
}
}];
// [blockOperation start]; // 启动任务
// 创建一个任务队列,NSOperationQueue
NSOperationQueue *operationQueue = [[NSOperationQueue new] autorelease];
// 当blockOperation中的任务执行完毕后,invocationOperation才开始执行
// [invocationOperation addDependency:blockOperation];
// 设置同时执行任务最大数
operationQueue.maxConcurrentOperationCount = 1;
// 添加任务到队列中
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:invocationOperation];
/**
* GCD 分发队列 遵守FIFO原则
* 串行队列:当一个任务执行完之后,才可以进行下一个任务的执行
* 并行队列:任务在派发的时候是有顺序的,不用等到上一个任务执行完成之后再执行,同时执行
*/
/**
* 脱离线程:当线程内部的代码执行完毕之后,线程自动关闭。例如:NSThread / NSObject
* 非脱离线程:当线程内部的代码执行完毕之后,线程不关闭,等待着下一次的使用。例如:NSOperationQueue
*/
// 1. GCD 使用主队列实现任务的串行,主队列分派的任务,永远在主线程中
dispatch_get_main_queue也是一种dispatch_queue_t。
dispatch_async( dispatch_get_main_queue(), ^{
NSLog(@"第一个任务:%d %@", i, [NSThread currentThread]);
});
// 2. 串行队列serial queues 按顺序同步访问,可创建任意数量的串行队列,各个串行队列之间是并发的。
当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。
// 参数1: 队列的标示
// 参数2: 类型
dispatch_async( dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"第一个任务:%d %@", i, [NSThread currentThread]);
});
// 3.1 使用自己创建的队列, 并行,执行的任务在不同的子线程中执行
dispatch_async(dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT), ^{
NSLog(@"第一个任务:%d %@", i, [NSThread currentThread]);
});
// 3.2 使用系统的globle队列,实现任务的并行,在子线程中
并行队列global dispatch queue,通过dispatch_get_global_queue获取,由系统创建三个不同优先级的dispatch queue。并行队列的执行顺序与其加入队列的顺序相同
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"第一个任务:%d %@", i, [NSThread currentThread]);
});
// 后台执行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
// 主线程执行:
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
// 一次性执行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// 延迟2秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
// 自定义dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
// your code
});
dispatch_release(urls_queue);
// 合并汇总结果
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行执行的线程一
});
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行执行的线程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
// 汇总结果
});
/**
* 1. 发送网络请求,获取网络数据的时候需要使用多线程(但是:我们创建网络请求的时候并不需要单独去开启子线程,系统会在方法内部帮我们做这件事)
* 2. 当有耗时很长的计算的时候,建议使用多线程
* 3. 当处理数据库中,或者其它位置大量数据的时候使用多线程
* 4. 总结:将耗时比较长的代码,都放到子线程中运行
* 5. 重点:页面的刷新必须回到主线程中
*/
一个应用GCD的例子:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"]; NSError * error; NSString * data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"call back, the data is: %@", data); }); } else { NSLog(@"error when download:%@", error); } });
GCD的另一个用处是可以让程序在后台较长久的运行。
在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
让程序在后台长久运行的示例代码如下:
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
// 在这里加上你需要长久运行的代码
[self endBackgroundUpdateTask];
}
- (void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
}
#pragma mark 循环
- (void)cycling:(id)obj
{
for (int i = 0; i < 10000; i++) {
NSLog(@"%@: %d", obj, i);
}
}
#pragma mark 发送网络图片请求
- (void)sendRequestForImageWithUrlStr:(NSString *)urlStr
{
// http://pic.sucaiw.com/up_files/bizhi/a09ba7547e/sucaiw-cdscbj4023.jpg
// 1. 直接通过NSData获取网络资源数据
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic.sucaiw.com/up_files/bizhi/a09ba7547e/sucaiw-cdscbj4023.jpg"]];
// 2. 更新图片
// 参数1: 主线程执行的方法
// 参数2: 传递给方法的参数
// 参数3: 是否等待子线程内执行完毕
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:[UIImage imageWithData:data]
waitUntilDone:YES];
}
#pragma mark - 更新页面
- (void)updateUI:(UIImage *)image
{
_imageView.image = image;
}
- (IBAction)buttonAction:(UIButton *)sender
{
// [self sendRequestForImageWithUrlStr:nil];
// 子线程中请求图片
// [self performSelectorInBackground:@selector(sendRequestForImageWithUrlStr:) withObject:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d", i);
}
});
}
- iOS GCD使用指南
-
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
Dispatch Queue
Dispatch Queue是用来执行任务的队列,是GCD中最基本的元素之一。
Dispatch Queue分为两种:
let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)
第一个参数是队列的名称,一般是使用倒序的全域名。虽然可以不给队列指定一个名称,但是有名称的队列可以让我们在遇到问题时更好调试;当第二个参数为nil时返回Serial Dispatch Queue,如上面那个例子,当指定为DISPATCH_QUEUE_CONCURRENT时返回Concurrent Dispatch Queue。
需要注意一点,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Queue将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放,如下:
let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)
dispatch_async(myQueue, { () -> Void in
println("in Block")
})
dispatch_release(myQueue)
以上是通过手动创建的方式来获取Dispatch Queue,第二种方式是直接获取系统提供的Dispatch Queue。
要获取的Dispatch Queue无非就是两种类型:
//获取Main Dispatch Queue
let mainQueue = dispatch_get_main_queue()
//获取Global Dispatch Queue
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
得到的Global Dispatch Queue实际上是一个Concurrent Dispatch Queue,Main Dispatch Queue实际上就是Serial Dispatch Queue(并且只有一个)。获取Global Dispatch Queue的时候可以指定优先级,可以根据自己的实际情况来决定使用哪种优先级。一般情况下,我们通过第二种方式获取Dispatch Queue就行了。
dispatch_after
dispatch_after能让我们添加进队列的任务延时执行,比如想让一个Block在10秒后执行:
var time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(10 * NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Void in
println("在10秒后执行")
}
NSEC_PER_SEC表示的是秒数,它还提供了NSEC_PER_MSEC表示毫秒。
上面这句dispatch_after的真正含义是在10秒后把任务添加进队列中,并不是表示在10秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。获取一个dispatch_time_t类型的值可以通过两种方式来获取,以上是第一种方式,即通过dispatch_time函数,另一种是通过dispatch_walltime函数来获取,dispatch_walltime需要使用一个timespec的结构体来得到dispatch_time_t。通常dispatch_time用于计算相对时间,dispatch_walltime用于计算绝对时间,我写了一个把NSDate转成dispatch_time_t的Swift方法:
func getDispatchTimeByDate(date: NSDate) -> dispatch_time_t {
let interval = date.timeIntervalSince1970
var second = 0.0
let subsecond = modf(interval, &second)
var time = timespec(tv_sec: __darwin_time_t(second), tv_nsec: (Int)(subsecond * (Double)(NSEC_PER_SEC)))
return dispatch_walltime(&time, 0)
}
这个方法接收一个NSDate对象,然后把NSDate转成dispatch_walltime需要的timespec结构体,最后再把dispatch_time_t返回,同样是在10秒后执行,之前的代码在调用部分需要修改成:
var time = getDispatchTimeByDate(NSDate(timeIntervalSinceNow: 10))
dispatch_after(time, globalQueue) { () -> Void in
println("在10秒后执行")
}
这就是通过绝对时间来使用dispatch_after的例子。
dispatch_group
可能经常会有这样一种情况:我们现在有3个Block要执行,我们不在乎它们执行的顺序,我们只希望在这3个Block执行完之后再执行某个操作。这个时候就需要使用dispatch_group了:let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void in
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("3")
}
dispatch_group_notify(group, globalQueue) { () -> Void in
println("completed")
}
输出的顺序与添加进队列的顺序无关,因为队列是Concurrent Dispatch Queue,但“completed”的输出一定是在最后的:12312
completed
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void in
println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void in
println("3")
}
//使用dispatch_group_wait函数
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
println("completed")
需要注意的是,dispatch_group_wait实际上会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。可以注意到dispatch_group_wait的第二个参数是指定超时的时间,如果指定为DISPATCH_TIME_FOREVER(如上面这个例子)则表示会永久等待,直到上面的Block全部执行完,除此之外,还可以指定为具体的等待时间,根据dispatch_group_wait的返回值来判断是上面block执行完了还是等待超时了。最后,同之前创建dispatch_queue一样,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Group将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。
dispatch_barrier_async
dispatch_barrier_async就如同它的名字一样,在队列执行的任务中增加“栅栏”,在增加“栅栏”之前已经开始执行的block将会继续执行,当dispatch_barrier_async开始执行的时候其他的block处于等待状态,dispatch_barrier_async的任务执行完后,其后的block才会执行。我们简单的写个例子,假设这个例子有读文件和写文件的部分:
func writeFile() {
NSUserDefaults.standardUserDefaults().setInteger(7, forKey: "Integer_Key")
}
func readFile(){
print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))
}
写文件只是在NSUserDefaults写入一个数字7,读只是将这个数字打印出来而已。我们要避免在写文件时候正好有线程来读取,就使用dispatch_barrier_async函数:NSUserDefaults.standardUserDefaults().setInteger(9, forKey: "Integer_Key")
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_barrier_async(globalQueue) {self.writeFile() ; self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
我们先将一个9初始化到NSUserDefaults的Integer_Key中,然后在中间执行dispatch_barrier_async函数,由于这个队列是一个Concurrent Dispatch Queue,能同时并发多少线程是由系统决定的,如果添加dispatch_barrier_async的时候,其他的block(包括上面4个block)还没有开始执行,那么会先执行dispatch_barrier_async里的任务,其他block全部处于等待状态。如果添加dispatch_barrier_async的时候,已经有block在执行了,那么dispatch_barrier_async会等这些block执行完后再执行。dispatch_apply
dispatch_apply会将一个指定的block执行指定的次数。如果要对某个数组中的所有元素执行同样的block的时候,这个函数就显得很有用了,用法很简单,指定执行的次数以及Dispatch Queue,在block回调中会带一个索引,然后就可以根据这个索引来判断当前是对哪个元素进行操作:let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_apply(10, globalQueue) { (index) -> Void in
print(index)
}
print("completed")
由于是Concurrent Dispatch Queue,不能保证哪个索引的元素是先执行的,但是“completed”一定是在最后打印,因为dispatch_apply函数是同步的,执行过程中会使线程在此处等待,所以一般的,我们应该在一个异步线程里使用dispatch_apply函数:let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(globalQueue, { () -> Void in
dispatch_apply(10, globalQueue) { (index) -> Void in
print(index)
}
print("completed")
})
print("在dispatch_apply之前")
dispatch_suspend / dispatch_resume
某些情况下,我们可能会想让Dispatch Queue暂时停止一下,然后在某个时刻恢复处理,这时就可以使用dispatch_suspend以及dispatch_resume函数://暂停
dispatch_suspend(globalQueue)
//恢复
dispatch_resume(globalQueue)
暂停时,如果已经有block正在执行,那么不会对该block的执行产生影响。dispatch_suspend只会对还未开始执行的block产生影响。
Dispatch Semaphore
信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。下面这个例子中使用了10条线程,但是同时只执行一条,其他的线程处于等待状态:let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let semaphore = dispatch_semaphore_create(1)
for i in 0 ... 9 {
dispatch_async(globalQueue, { () -> Void in
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
let time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Void in
print("2秒后执行")
dispatch_semaphore_signal(semaphore)
}
})
}
取得信号量的线程在2秒后释放了信息量,相当于是每2秒执行一次。通过上面的例子可以看到,在GCD中,用dispatch_semaphore_create函数能初始化一个信号量,同时需要指定信号量的初始值;使用dispatch_semaphore_wait函数分配信号量并使计数减1,为0时处于等待状态;使用dispatch_semaphore_signal函数释放信号量,并使计数加1。另外dispatch_semaphore_wait同样也支持超时,只需要给其第二个参数指定超时的时候即可,同Dispatch Group的dispatch_group_wait函数类似,可以通过返回值来判断。这个函数也需要注意,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Semaphore将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。
dispatch_once
dispatch_once函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次,如果我们要通过dispatch_once创建一个单例类,在Swift可以这样:class SingletonObject {
class var sharedInstance : SingletonObject {
struct Static {
static var onceToken : dispatch_once_t = 0
static var instance : SingletonObject? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SingletonObject()
}
return Static.instance!
}
}
这样就能通过GCD的安全机制保证这段代码只执行一次。 -
http://blog.sina.com.cn/s/blog_45e2b66c01010dhd.html
1。GCD之dispatch queue
http://www.cnblogs.com/scorpiozj/archive/2011/07/25/2116459.html
2。iOS中GCD的魔力
http://blog.csdn.net/favormm/article/details/6453260
3。官方 ,内容真的很多
http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1
4.详解IOS开发应用之并发Dispatch Queues
http://mobile.51cto.com/iphone-283323.htm
5。斯坦福大学关于gcd的讲义
http://www.stanford.edu/class/cs193p/cgi-bin/drupal/system/files/lectures/Lecture 13_0.pdf
gcd其实就是牛逼简化版的多线程。gcd和block是亲兄弟,所以学习gcd前需要了解block,不知道也没事,看看代码就明白了。
GCD是和block紧密相连的,所以最好先了解下block(可以看我之前收藏的一篇文章).GCD是C level的函数,这意味着它也提供了C的函数指针作为参数,方便了C程序员.
下面首先来看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明异步运行,block代表的是你要做的事情,queue则是你把任务交给谁来处理了.(除了async,还有sync,delay,本文以async为例).
之所以程序中会用到多线程是因为程序往往会需要读取数据,然后更新UI.为了良好的用户体验,读取数据的操作会倾向于在后台运行,这样以避免阻塞主线程.GCD里就有三种queue来处理.
1. Main queue:
顾名思义,运行在主线程,由dispatch_get_main_queue获得.和ui相关的就要使用Main Queue.
2.Serial quque(private dispatch queue,其中dispatch_queue_t就是一种)
每次运行一个任务,可以添加多个,执行次序FIFO. 通常是指程序员生成的,比如:
NSDate *da = [NSDate date]; NSString *daStr = [da description];
const char *queueName = [daStr UTF8String];
dispatch_queue_t myQueue = dispatch_queue_create(queueName, NULL);
3. Concurrent queue(global dispatch queue,其中dispatch_time_t就是一种):
可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.使用dispatch_get_global_queue获得.
所以我们可以大致了解使用GCD的框架:
dispatch_async(getDataQueue,^{ //获取数据,获得一组后,刷新UI. dispatch_aysnc (mainQueue,^{ //UI的更新需在主线程中进行 }; } )
在ios,blocks是对象,它封装了一段代码,这段代码可以在任何时候执行。Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:blocks是inline的,并且它对局部变量是只读的。
Ios4已经直接支持blocks,
Blocks的定义:
int (^Multiply)(int, int) = ^(int num1, int num2) {return num1 * num2;};
定义了一个Multiply的blocks对象,它带有两个int参数,返回int。等式右边就是blocks的具体实现,注意{}blocks体里的;。
Blocks可以访问局部变量,但是不能修改。
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
multiplier ++;//编译报错
return num * multiplier;
};
如果要修改就要加关键字:__block
__block int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
multiplier ++;//这样就可以了
return num * multiplier;
};
作为函数的参数,blocks某种意义上替代了回调函数或者delegate。当函数调用了,假设某个事件触发,这时blocks里的内容就会运行。这样有利于代码的整合和阅读,你不需要到处去实现委托方法了。
系统API中已经有很多支持blocks参数了
· Completion handlers
· Notification handlers
· Error handlers
· Enumeration
· View animation and transitions
· Sorting
函数原型
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明异步运行,.(除了async,还有sync,delay,本文以async为例).
queue则是你把任务交给谁来处理了
block代表的是你要做的事情
queue有三种
Main: tasks execute serially on your application’s main thread Concurrent: tasks start executing in FIFO order, but can run concurrently. Serial: tasks execute one at a time in FIFO order
- (1)serial queues(串行队列)又称私有调度队列(private),一般用再对特定资源的同步访问上。我们可以根据需要创建任意数量的串行队列,每一个串行队列之间是并发的。
- (2)并行队列,又称global dispatch queue。并行队列虽然可以并发的执行多个任务,但是任务开始执行的顺序和其加入队列的顺序相同。我们自己不能去创建并行调度队列。只有三个可用的global concurrent queues。
- (3)main dispatch queue 是一个全局可用的串行队列,其在行用程序的主线程上执行任务。此队列的任务和应用程序的主循环(run loop)要执行的事件源交替执行。因为其运行在应用程序的主线程,main queue经常用来作为应用程序的一个同步点
先看一段代码
- @interface UIImageView (DispatchLoad)
- - (void) setImageFromUrl:(NSString*)urlString;
- - (void) setImageFromUrl:(NSString*)urlString
- completion:(void (^)(void))completion;
- @end
- #import "UIImageView+DispatchLoad.h"
- @implementation UIImageView (DispatchLoad)
- - (void) setImageFromUrl:(NSString*)urlString {
- [self setImageFromUrl:urlString completion:NULL];
- }
- - (void) setImageFromUrl:(NSString*)urlString
- completion:(void (^)(void))completion {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- NSLog(@"Starting: %@", urlString);
- UIImage *avatarImage = nil;
- NSURL *url = [NSURL URLWithString:urlString];
- NSData *responseData = [NSData dataWithContentsOfURL:url];
- avatarImage = [UIImage imageWithData:responseData];
- NSLog(@"Finishing: %@", urlString);
- if (avatarImage) {
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = avatarImage;
- });
- dispatch_async(dispatch_get_main_queue(), completion);
- }
- else {
- NSLog(@"-- impossible download: %@", urlString);
- }
- });
- }
- @end
以上代码主要是实现,图像异步加载。
分解一下:
1>添加到gcd队列
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
这个代码主要实现,将图像加载block添加到queue队列中,
- dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
这个是获取全局并行队列(global dispatch queue),系统队列,只有3个。
2>加载图像。这个滤过
3>通知或更新主线程
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = avatarImage;
- });
- dispatch_async(dispatch_get_main_queue(), completion);
“block的一个优势是可以使用其自己作用域外的变量,例如,一个block可以读取其父作用域的变量值,此值是copy到了block heap的数据结构中。当block被加入到dispatch queue中,这些值通常为只读形式。”
而更新UI只能在主线程中实现,所以调用主线程函数 completion
这样就完成了异步加载图像的流程。
当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。
和并发队列不同,我们要自己去创建和管理串行队列,可以创建任意数量的串行队列。当我们创建串行队列时,应出于某种目的,如保护资源,或者同步应用程序的某些关键行为。
下面的代码表述了怎么创建一个自定义的串行队列,函数dispath_queue_create需要两个参数,队列的名字,队列的属性。调试器和性能工具显示队列的名字帮助我们去跟踪任务是如何执行,队列的属性被保留供将来使用,应该为NULL
- dispatch_queue_t queue;
- queue = dispatch_queue_create("com.example.MyQueue", NULL);
除了自己创建的自定义队列,系统会自动的给我创建一个串行队列并和应用程序的主线程绑定到一起。下面讲述如何获得它。
贴几段斯坦福大学关于gcd的代码,这段代码逐步演示了如何修正错误,其中用到的既是串行队列
1。这个是原始代码
- - (void)viewWillAppear:(BOOL)animated
- {
- NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];
- UIImage *image = [UIImage imageWithData:imageData];
- self.imageView.image = image;
- self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
- self.scrollView.contentSize = image.size;
- }
2。这个是采用gcdd的代码,里面有错误3处
- - (void)viewWillAppear:(BOOL)animated
- {
- dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);
- dispatch_async(downloadQueue, ^{
- NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];
- UIImage *image = [UIImage imageWithData:imageData];
- self.imageView.image = image;
- self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
- self.scrollView.contentSize = image.size;
- });
- }
3。第一个错误,UI更新只能在主线程中 Problem! UIKit calls can only happen in the main thread!
改正后如下:
- - (void)viewWillAppear:(BOOL)animated
- {
- dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);
- dispatch_async(downloadQueue, ^{
- NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];
- dispatch_async(dispatch_get_main_queue(), ^{
- UIImage *image = [UIImage imageWithData:imageData];
- self.imageView.image = image;
- self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
- self.scrollView.contentSize = image.size;
- });
- }); }
4。第二个错误,NSManagedObjectContext并不是线程安全的,gcd中访问成员变量有危险
Problem! NSManagedObjectContext is not thread safe,
so we can’t call photo.URL in downloadQueue’s t
改正后如下:
- - (void)viewWillAppear:(BOOL)animated
- {
- NSString *url = photo.URL;
- dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);
- dispatch_async(downloadQueue, ^{
- NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:url];
- dispatch_async(dispatch_get_main_queue(), ^{
- UIImage *image = [UIImage imageWithData:imageData];
- self.imageView.image = image;
- self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
- self.scrollView.contentSize = image.size;
- }); });
- }
5。第三个错误,队列创建后没有释放,内存泄露
- - (void)viewWillAppear:(BOOL)animated
- {
- NSString *url = photo.URL;
- dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);
- dispatch_async(downloadQueue, ^{
- NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:url];
- dispatch_async(dispatch_get_main_queue(), ^{
- UIImage *image = [UIImage imageWithData:imageData];
- self.imageView.image = image;
- self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
- self.scrollView.contentSize = image.size;
- }); });
- dispatch_release(downloadQueue); //won’t actually go away until queue is empty }
分类: MutiThread