接上文:关于多线程和GCD新手教程(一)
一个简单的方式就是从你的代码中的一部分刷新另外一部分代码,这是苹果内置的NSNotification消息系统.它的确很简单.你可以通过[NSNotificationCenter defaultCenter]取得NSNotificationCenter(消息中心)的单例而且:
1.如果你有想要刷新UI或代码的地方,你应该调用postNotificationName方法.你仅仅需要提供一个独特的字符串(例如:com.razeware.imagegrabber.imageupdated)以及一个对象(例如你刚完成下载的图片类ImageInfo)
2.如果你想找出这个更新什么时候发生,你可以调用addObserver:selector:name:object方法.在我们的样例中 ImageListViewController 会注意更新什么时候发生,所以它可以适当的加载tableview的cell.放置该函数最好的地方就是在viewDidLoad:中
3.当视图将要销毁不要忘记调用removeObserver:name:object .否则消息系统可能会在你销毁的视图(或者更坏的情况是未分配内存的对象)调用其他方法,这种事情的后果很严重.
所以我们来试着解决它.打开ImageInfo.m 并且做出如下修改
// Add inside getImage, right after image = [[UIImage alloc] initWithData:data];
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.razeware.imagegrabber.imageupdated" object:self];
图片一旦被下载,我们就会发送一个通知并且将这个刚更新的对象传过去.
下一步转到ImageListViewController.m 并且做出如下修改:
// At end of viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageUpdated:) name:@"com.razeware.imagegrabber.imageupdated" object:nil];
// At end of viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.razeware.imagegrabber.imageupdated" object:nil];
// Add new method
- (void)imageUpdated:(NSNotification *)notif {
ImageInfo * info = [notif object];
int row = [imageInfos indexOfObject:info];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSLog(@"Image for row %d updated!", row);
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
在viewDidUnload方法中关于通知的注册器,简单来说就是:”当消息到来的时候请调用imageUpdate”.同样我们也可以在viewDidUnload方法中适当的取消注册器.
调用imageUpdate本质上就是对象中传递的Imageinfo的数组.一旦找到了它,我们就能得到它是哪一行并且告诉tableview来更新那一行.
编译运行之后你就会看到图片在他们下载完毕之后会跳出.
GCD和调度队列,我的天!
我们的应用中仍然会有一个问题.如果你点击了”Grab!”并且在detail视图加载的时候持续的上下滑动,在zip文件下载的时候你会看到全部的UI忧郁存储和解压zip文件而被冻住.
这是由于在ASIHTTPRequest中的完成块在主线程中被调用,我们调用这段代码来在主线程中解决这些问题:
[request setCompletionBlock:^{
NSLog(@"Zip file downloaded.");
NSData *data = [request responseData];
[self processZip:data sourceURL:sourceURL]; // Ack - heavy work on main thread!
}];
我们如何才能让这种高负荷的工作运行在后台呢?
在ios3.2介绍了一个非常简单(也非常有效)的方式通过GCD系统来解决这个问题.从根本上来说,无论何时当你想在后台运行一些东西的时候,你可以调用dispatch_async并且写入一些你想运行的代码.
GCD将会为你解决所有的细节-它会在你需要的时候为你创建一个新的线程,或者重用一个以前的可用线程.
当你调用dispatch_async,你就会进入一个调度队列(dispatch_queue).你可以把这个想成一个先进先出列表来存储所有你写入的代码块.
你可以创建你的调度队列(通过dispatch_create).或者获得一个特别的主线程队列(通过dispatch_get_main_queue).我们可以创建一个称谓”backgroundQueue”的后台队列,用之来在后台运行进程任务,像解析XML或者存储/解压zip文件.
线程队列,加锁,以及猫粮
当一个调度队列被创建时它默认是连续的-这意味着在同一时间只有只有一块代码可以在队列中运行.这种方式确实会很便捷,因为你可以用这种方式来保护你的数据.
如果你不是非常熟悉多线程加锁,想想我们之前猫的例子.如果两只猫同时都想到猫粮盒中吃猫粮会发生什么问题?这就是那个大问题!
但是如果我们使所有的猫都排成一列.然后我们说:”喵咪们,如果你们想接近这个盘子,你们必须站在这条线里!”只要是这样事情就简单多了!
那就是使用调度队列来保护数据的基本想法.当你编写代码以便使一个特殊的数据结构只能被在调度队列中运行的代码使用.然后随着调度队列连续运行,你会保证只有一个代码块会在同一时间中访问这个数据结构.
在这个应用中我们需要保护两个数据结构:
1.在ImageListViewController中的linkUrls数组 .为了保护它,我们编写代码来使它只能在主线程中运行.
2.在ImageManager中的pendingZips变量.为了保护它,我们编写代码让它只能在后台队列中运行.
关于GCD我们聊的已经足够了-让我们来试试它!
GCD练习
让我们从打开ImageGrabber.h开始并且做出如下改变:
// Add to top of file
#import <dispatch/dispatch.h>
// Add new instance variable
dispatch_queue_t backgroundQueue;
为了使用GCD,首先需要导入.我们也需要预先编译调度队列来运行我们的后台进程任务.
下一步打开ImageGrabber.m并且做出如下变化:
// 1) Add to bottom of initWithHTML:delegate
backgroundQueue = dispatch_queue_create("com.razeware.imagegrabber.bgqueue", NULL);
// 2) Add to top of dealloc
dispatch_release(backgroundQueue);
// 3) Modify process to be the following
- (void)process {
dispatch_async(backgroundQueue, ^(void) {
[self processHtml];
});
}
// 4) Modify call to processZip inside retrieveZip to be the following
dispatch_async(backgroundQueue, ^(void) {
[self processZip:data sourceURL:sourceURL];
});
// 5) Modify call to delegate at the end of processHTML **AND** processZip to be the following
dispatch_async(dispatch_get_main_queue(), ^(void) {
[delegate imageInfosAvailable:imageInfos done:(pendingZips==0)];
});
这些都是很简单但是却很重要的调用,让我们来挨个讨论一下他们.
1.这条创建了一个调度队列.当你创建了一个调度队列时需要给他一个独特的名字(字符形式)
2.当你创建了一个调度队列,不要忘记释放释放它!对于这个队列,我们需要在ImageManager销毁的时候释放它
3.旧的进程会立即运行processHTML,因此在主线程中运行解析HTML的时候它会阻塞UI刷新.现在我们将它运行在我们创建的后台队列中,只要简单的调用dispatch_async!
4.相似的,在我们下载zip之后我们会在主线程中得到一个ASIHTTIRequest的回调:”hey,我做完了”.作为我们之前存储以及解压zip文件阻塞UI刷新的替代,我们现在将它运行在后台队列.这对于保证pendingZips变量也很重要.
5.我们想要确保我们在主线程上下文中调用代理方法.首先根据我们之前的策略,为了保证在viewcontroller中的linkURLs数组只能通过主线程访问.其次这个方法会和UIKit对象相互作用,UIKit对象只能被主线程使用.
那正是要点!编译并且运行你的代码.imageGrabber应该表现的更出色.
http://cdn1.raywenderlich.com/wp-content/uploads/2011/07/ResponsiveApp.jpg
但是等一下!
如果你只是暂时的开发ios,你可能会听到这些被称为NSOperations以及操作队列的想法.你可能会疑惑什么时候使用它们,什么时候该使用GCD.
NSOperations只是一个基于GCD的简单的API.所以当你在使用NSOperation时,其实你仍旧在使用GCD.
NSOperations会给你一些你喜欢的特点.你可以创建一些基于其他操作的操作,在你提交队列之后重新排列你的队列以及类似的特点.
事实上,ImageGrabber 已经使用了NSOperations和操作队列!ASIHTTPRequest在底层使用它们,你可以把操作队列配置成你想要的那样.
所以你该使用哪个?取决于你的应用,关于这个应用,他比较简单所以我们只需要使用GCD,不需要其他的NSOperation的特点.但是如果你在应用中需要这些特点,快点使用它吧!