多线程和GCD初学者教程
原文链接:http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
当不使用多线程时,UI(主线程)经常变得不可响应. 该教程针对有相关开发经验者介绍多线程,如果是新手,请先查看更多教程.
一.为什么关注?
首先通过一个没有用多线程的实际的例子进行说明.
下载该工程,用xcode打开并运行,你将在屏幕上看到一个免费的游戏包(网页).
这个app的任务是把该网页中的所有图片都下载下来,并把它们在一个tableview中显示出来.
运行后你会发现该app经常无法响应用户操作,如grap按钮.
因为该app解析html,下载图片和zip文件,解压等操作都在主线程中进行.
二.多线程...和猫!(作者爱猫?)
如果你已经熟悉多线程的概念,那么就跳到下一节吧,否则,请继续.
当你想像程序的运行时,你可以认为是一只带着箭头的猫,这箭头指向某一行代码....(汗). 这只猫移动这个箭头使得程序运行.
app的关键问题是这只可怜的猫已经精疲力尽了,因为它又要管UI(响应用户事件)和大量数据处理.
多可怜啊,它需要休息.
解决文案很简单:再买几只猫.(买猫怕养不活哦).
现在,你的第一只猫负责更新UI和响应用户事件,而其它猫负责暗地里的下载文件,解析html等等等等.
这个就是多线程的要旨.
在ios中,你经常要实现的方法如viewDidLoad,button tap callbacks等都运行在主线程中,不要把费时的工作放到主线程中,不然你会得到一个不响应UI和精疲力尽的猫.
三.孩子,不要在家做这个.
让我们看看现有的代码并且讨论它是怎么工作的.
app中的rootViewController是WebViewController,当你点grabTapped按钮,它获取当前页的html并传给imageListViewController.
在imageListViewController中的viewDidLoad中,它创建一个新的imageManager并处理之.这个类,还有imageInfo,包含了所有的费时代码.
让我们看看这两个类是如何工作的:
1.ImageManager:processHTML:用正则表达式匹配来搜索html中的链接.
2.ImageInfo:initWithSourceURL:调用getImage来获取图片,通过[NSData dataWithContentsOfURL:..]同步进行.
3.ImageInfo:retrieveZip:同2来获取zip文件
4.ImageInfo:processZip:调用zipArchieve库进行解压缩.
其它的一些方法就不介绍了.主要就是理解它如何工作的,以便让我们改进.
四.异步下载.
首先让我们用异步下载来代替这个最慢的操作--下载文件.
其实用苹果内建的NSURLRequest和NSURLConnection并不难,但是使用ASIHTTPRequest更简单.
下载和添加该类就不介绍了.
现在是时候进行替换了.
在ImageManger.m中:
// Add to top of file #import "ASIHTTPRequest.h" // Replace retrieveZip with the following - (void)retrieveZip:(NSURL *)sourceURL { NSLog(@"Getting %@...", sourceURL); __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL]; [request setCompletionBlock:^{ NSLog(@"Zip file downloaded."); NSData *data = [request responseData]; [self processZip:data sourceURL:sourceURL]; }]; [request setFailedBlock:^{ NSError *error = [request error]; NSLog(@"Error downloading zip file: %@", error.localizedDescription); }]; [request startAsynchronous]; }
该代码实现了当异步下载后可能要执行的两个Block.
同理,在ImageInfo.m中
// Add to top of file #import "ASIHTTPRequest.h" // Replace getImage with the following - (void)getImage { NSLog(@"Getting %@...", sourceURL); __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL]; [request setCompletionBlock:^{ NSLog(@"Image downloaded."); NSData *data = [request responseData]; image = [[UIImage alloc] initWithData:data]; }]; [request setFailedBlock:^{ NSError *error = [request error]; NSLog(@"Error downloading image: %@", error.localizedDescription); }]; [request startAsynchronous]; }
运行之,app马上转到detail tab,但是还有一个问题:图片没有显示.
五.介绍NSNotifications.
传更新从一段代码到另一段代码,使用NSNotification是一种很简单的方法.
1.当你完成一任务后想通知给其它代码段,调用postNotificationName.
2. 当你想知道更新是否完成时,使用addObserver:selector:name:object.
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]; }
这样,图片更新就完成了.
六.Grand Central Dispatch and Dispatch Queues.
这样还有一个问题:解压zip还在主线程中.
有空再翻译....