由于想学习一下多线程编程,所以在百度搜索了一下,找到了一个外国网址- -那么就来翻译一下并且学习吧
为初学者准备的多线程和GCD教程
你是否曾经因为想要做什么事情而试着去编写一个app,而在UI没有回应的时候会有一个长时间的暂停.
这就是你的app需要多线程的标识.
在这个教程中,你将会着手于ios上的核心多线程API:GCD(多线程优化技术)
你可能会写一个没有使用多线程的应用(从此之后你的应用会十分迟钝…),一旦你通过使用多线程技术来将它改变.你会惊讶于巨大的差异.
这个教程假定你对基础ios开发有一定的熟悉.如果你是一个ios开发小白,你应该在笨网站上查阅其他教程.
言归正传,喝一口苏打或者嚼口香糖同时开始学习这个教程.—你已经踏上了多线程的路
我为什么要关心?
“啊,你为什么要告诉我这些?我为什么要关心这个?我一点也不关心.你今天中午想要吃什么?”
你就像一个木偶一样,你可能仍然会怀疑你为什么要关心这些多线程的问题.
So,让我来给你展示一个没有使用多线程的例子.
下载这个程序,用xcode打开编译,运行,你将会看到一个由vickiwenderlich.com返回的免费Game Artpack显示在屏幕上
测试的时候十分卡,刷了好长时间
这个app叫ImageGrabber,它的作用就是通过HTML访问这个网页,并且检索这些链接图片,然后将他们显示到tableview上以便你能更近的观察他们.
最炫酷的部分就是他能下载zip文件并且查看zip文件里的图片,就像这个网页上的free game art zip
继续然后点击”Grab”来观察他运行
…
…等待…
…
…等待…
…
…等待…
…
它成功了,但是时间太长了…这个app解析html,下载图片和zip文件,并且解压zip文件,全部是在主线程中进行的.
最后的结果就是用户需要呆坐在那里很长时间,并且不确定这个app是否在工作.
这样会导致很悲惨的后果:用户可能推出app,系统可能会因为app占用时间太长而终止app运行,或者你可能会得到一个生气的tomato攻击你的树屋.
幸运的是,多线程来拯救你了,我们通过把这些在主线程中高强度的运算使用一些苹果提供的简单API来移动到后台.
多线程…和猫
如果你已经熟悉了多线程的概念,感觉这十分轻松想要跳到下一部分.事实上,你仍然是个新手—继续读下去
你认为一个程序在运行时,你可以把它看做一只用大箭头指向当前行的猫.猫随着程序的前进移动箭头.
ImageGrabber app产生的问题是:我们通过把所有事情都在主线程中运行,导致我们的猫精疲力竭.所以在app重画UI或者响应事件之前,他需要花费所有时间来集中完成这些高强度工作,例如下载文件,解析html等等
So我们怎么样才能让不堪重负的猫休息一会呢?解决方案很简单–买更多只猫(事实上,我有一个朋友非常擅长这个)
那种方法,你主要的猫可以响应UI的刷新以及响应其他用户事件,同时你的剩余的猫去后台下载文件,解析html,跳到桌面上(结束)
这就是多线程设计的要旨.就像这些猫东奔西跑执行任务,一个进程被分解成很多线程去执行.
在IOS上,你需要声明的方法(例如viewDidLoad,buttontap callbacks)都运行在主线程中.你不会想在主线程中执行密集的工作任务,否则你将会得到一个没有响应的UI和一个快要累死的猫….
孩子们,不要在家做这些
让我们来看一下代码,讨论一下它是怎么工作的-和它为什么差劲!
这个app的根视图控制器是WebViewController.当你点击(grab)键时,获取当前页面的HTML,并且将它传递给ImageListViewController
. 在ImageListViewController的viewDidLoad中,它创建了一个新的ImageManager 并且调用进程 .这个类连同ImageInfo一起,包含所有的耗时运行的代码,例如解析HTML,将图片从网上下载下来,解压文件
让我们来看看这两个文件是怎么工作的:
ImageManager:processHTML:使用正则表达式在html中匹配搜索到得链接.这个可能潜在的耗费时间,取决于html有多大.对于它找到的每一个zip文件,都会调用retrieveZip.对于找到的图像文件,它会通过initWithSourceUrl初始化创建一个新的ImageInfo实例
ImageInfo:initWithSourceURL:通过使用异步方法[NsData dataWithContentsOfURL]调用getImage来取回网络上的图片.有很多例如[NsDatadataWithContentsOfURL]的方法,这些方法封锁执行时的流(the flow of execution)直到它完成,可能会花费很长时间,你几乎不会再在你的app中使用这些方法.
ImageInfo:retrieveZip:和以上所述的相似,使用可怕的[NSData dataWithContentsOfURL:...],这个东西会阻塞主线程直到它完成.当它完成的时候,它会调用processZip.
ImageInfo:processZip:使用ZipArchive库来把下载数据存储到硬盘中,并解压它,寻找里面的图片.写入硬盘和像这样解压是个很慢的操作,所以是另外一个不应该放到主线程中的实例.
你可能会注意到一些调用ImageManager的的方法-imageInfoAvailable.这是在当table中有新的入口(?)的时候ImageManager 通知table view的方法
观察一下并且确保你理解当前的执行流-(它为什么这么差劲).你也可能会觉得它运行时有用并且在它运行时观察控制台输出日志,你将会在代码运行时看到nolog的输出.
如果你对它的当前工作有了良好的认识,让我们继续并且用多线程来提升它.
异步下载
让我们来使用异步调用来代替这些超级慢的操作 – 下载文件.事实上,在使用苹果的类-NSURLRequest 和NSURLConnection,下载文件并不难.但是我是一个对于可以使这更简单的外包类-ASIHTTPRequest的粉丝.
我们将要使用这个来异步下载文件,所以把它添加到你的工程中.
如果你没有ASIHTTPRequest,先下载它.当你下载完毕后,在groups andfile中右击ImageGrabber工程入口,选择New Group,将新的group命名为ASIHTTPRequest,然后把ASIHTTPRequest\Classes directory(ASIAuthenticationDialog.h和其他的, 最重要的是不要把子文件夹移动例如 ASIWebPageRequest, CloudFiles, S3, and Tests.)拖拽到ASIHTTPRequest的group中.确保“Copy itemsinto destination group’s folder (if needed)”勾选.点击结束
对于ASIHTTPRequest\External\Reachability这两个文件重复上述操作,因为这些是本工程的依赖.
添加ASIHTTPRequest的最后一步是,你需要吧一些你需要的框架导入,点击ImageGrabber入口中得Groups &Files,点击PromoTest target,选择Build Phasestab 和expand the Link Binary with Libraries部分.点击添加按钮,选择CFNetwork.framework.然后添加SystemConfiguration.framework和MobileCoreServices.framework.
现在是时候来用异步的优秀代码来代替差劲的同步代码!
打开ImageManager.m做出如下改变
#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]; }
这里通过一个给定的URL来创建ASIHTTPRequest,它创建了一个当请求结束或者因某些原因请求失败,运行的方法的代码块.
然后调用startAsynchronous.这个方法可以迅速返回,所以主线程可以在UI变化和相应用户输入持续运行.同时,系统在后台线程会自动运行代码下载zip文件,当它完成或者失败的时候会调用代码块.
相似的改变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]; }
这和其他代码更相似-它在后台运行下载,当完成的时候改变image变量
让它运行一下!编译运行然后点击”Grab” –他可以快速地生成详细tab而不是经历一个长时间的暂停!但是,仍然有一个主要的问题:
图片在它们下载完毕后并没有在tableview中显示!你可以通过向上或者想下滑动table来显示他们.但是这是一种bug(?),我们怎么才能修复它?