iOS 多线程和GCD(Grand Central Dispath) 教程 (一)

article/2025/1/11 9:59:35
iOS 多线程和GCD(Grand Central Dispath) 教程 (一) 

本文翻译自 Ray Wenderlich 的博客 点击打开原文链接。全部由本人亲手翻译...童叟无欺~

你有木有遇见过这样的情况,当你在写app的时候,有时候界面就卡住了,要等很长时间,而这段时间你什么也不能做,因为界面不会有任何反应。

这,说明一个问题!少年,你的app该使用多线程了!

在本文中,你会了解到iOS中的核心多线程API:Grand Central Dispath --  就是我们俗称的GCD。

如果你耐心看完这篇文章,并且跟着操作,你会将一个没有启用多线程的卡到爆的app修改成一个使用多线程操作的app。你一定会震惊的!

这篇文章假装你是了解iOS的基础开发的(当然,你自己千万不要假装,不然看不懂别怪我...)。如果你假装不了,那快去学基础吧,额...我这里木有...请自己觅食!

事不宜迟,来三碗烧酒或半斤牛肉,我们开始学习吧 - 呦!你已经开始在多线程了喔~

我应该关心什么?

   “卧槽,这啥玩意东西?我关心它干嘛!我才不管,哥几个,晚上来几圈?!”

如果你就是个搬砖的,那你肯定还搞不明白我们为什么要用多线程。

睁大你的25K氪金狗眼!看看下面这个例子,它根本就没用多线程!

下载这个项目,在xcode中打开它,编译-运行,你将看见一个网页,显示在屏幕上。(如果没有,请打开源码,换个网址,这丫的网址是国外的,估计被墙了。)像下面这样:

ImageGrabber - an unresponsive and synchronous sample app
这个app叫做:《图片收集器》 它的工作就是通过打开一个HTML的web页面,然后检索所有的链接到的图片,然后把这些图片展示在tableview上,这样你会更加清晰的看到它们。(话说我完全不知道除了好玩,这个app能干嘛...)

这个app最棒的地方是在于它甚至可以下载压缩包文件,然后查看压缩包文件里面的图片!

点击左下角的“Grab”(收集)按钮,看看它会干什么!

...

...这........

...

...到底要........

...

...等多久.......

...


要疯了!!

———————————————————————好—玩—的—分—割—线——————————————————————

我尼玛... 终于好了呀!但是这一辈子都过去了啊!搞毛?!这个app解析了HTML,下载了图片,下载了压缩包文件,然后打开了压缩包,然后又检索了图片...这全是在主线程搞的事情!

有耐心等你这个程序运行结束的用户一定都是真爱!

这个结果是你无法承受的:操作系统会觉得你这破玩意运行的时间太长了,算了,给它退了吧!然后用户直接退出然后秒删你的app!然后你出门都会被砸臭鸡蛋!(不就写了个程序么...至于这样么....)

但是,孩子不哭!多线程来拯救你!我们不把所有这些繁重的任务都交给主线程来完成,我们将使用苹果给我们提供的简单的API来将它们放在暗中偷偷运行!

多线程...和猫?!

    如果你对多线程已经有了概念,就请欢快的跳过这一部分,但是,如果你没有 - 继续看!

当你的程序在运行的时候,你可以将它想想成你只猫带着一个大大的箭头指着正在运行的代码,猫合箭头的移动就是你代码运行的逻辑,一次一步。

Multithreading is like a cat with an arrow.


多线程就像是一大堆的猫猫带着一大堆的箭头。

———————————————————————可—爱—的—分—割—线——————————————————————

这个图片收集的app的问题是我们让这只可怜的小猫猫在主线程做了全部的任务可怜,好可怜!所以在猫猫画界面之前,你先让他去做其他密集的任务,像是下载文件、解析HTML等等等等。

Don't overwork your cat - or the main thread!

不要让你的小猫猫工作过度了!(不要再主线程操作太多!)

然后,我们该怎样让我们辛勤的猫咪员工休息休息呢?那就是,招更多的喵咪员工啊!

这样你的第一只猫咪就可以专职负责画界面和响应用户的操作了,这个时候你其他的喵咪同时在背后悄悄的下载文件,解析HTML和跳到桌子上。(走开!)

这就是多线程的一个大概主旨,就像那些在背后搞动作的猫咪一样,一个任务可以被分解成多个线程。

在iOS中,你用到的例如viewDidLoad,buttonClicked等方法都是必须在主线程运行的。你永远不会想要主线程去处理密集的工作,否则你的界面就会卡住没有反应,当然,你还会得到一只工作过度的猫...

少年!千万不要这么干!

现在我们看看我们目前的代码然后讨论一下,为什么它会这么糟?!

这个app的根视图控制器就是一个webViewController。当你点击了“收集”按钮的时候,它将得到当前页面的HTML代码,然后将它传递给ImageListViewController。

在ImageListViewController的viewDidLoad方法中,它会创建一个新的ImageManager然后调用其方法。这个类,独立于ImageInfo,它有很多动作,像解析HTML,将图片从网上下载下来,然后解压文件。

让我们看看这两个文件是怎么协同工作的:

· ImageManager:processHTML: 用正则表达式搜索查找HTML中的链接。它会是一个潜在的花费时间的操作,这决定于小子你看的网页是什么样的偷笑。它找到的每一个压缩文件,他都会调用retrieveZip(解压压缩文件)。对于每一个它寻找到的图片,它都会创建一个ImageInfo对象,并且调用initWithSourceURL方法进行初始化。

·ImageInfo:initWithSourceURL: 调用 getImage 方法和同步方法[NSData dataWithContentsOfURL:]来从网络上检索图片。就像[NSString stringWithContentsOfURL:…]方法,这个方法会阻止一切数据流动直到它自己完成,这会花费很长时间导致你根本就不想在你的app里面用这些方法。

·ImageInfo:retrieveZip: 跟上面的方法一样,使用了会停止一主线程直到它自己完成的可怕的[NSData dataWithContentsOfURL:]方法 。当这个方法完成后,它会调用processZip(检索压缩包);

·ImageInfo:processZip: 使用ZipArchive库去将下载下来的数据保存到硬盘上,然后解压,然后查看是不是有图片在里面。将数据写到硬盘然后解压看起来就是个浪费时间的过程,这又将是主线程的一个大工程。

你应该也发现了ImageManager会调用它的代理方法:imageInfosAvailable。这就是当有新的数据出现ImageManager怎样提醒tableview刷新界面。

仔细想想现在的执行的逻辑流程,想想为什么会这么差?你可以通过查看控制台等方式看看代码是怎么运行的。

一旦你搞明白了现在它是怎么运行的,就快继续看怎么使用多线程来优化它吧!

下载一些第三方的库来完成部分异步

原文已经很早了,说是可以使用ASIHttpRequest,不过目前这个库已经没有人维护了,所以我们可以使用其他的东西,例如:MKNetWorkKit。就先用ASIHttpRequest做例子吧。

我们使用ASIHttpRequest来优化下载数据的过程。

在xcode中打开刚才的app,打开 ImageManager.m 然后做如下修改:

// 添加在文件头部
#import "ASIHTTPRequest.h"// 使用这个方法代替原方法
- (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];    
}

这种方式就不多赘述了,如果你的本领已经到了看多线程的时候,你应该也能看懂上面的代码。

相同的,点击打开 ImageInfo.m 然后做一些修改:

// 添加到文件的头部
#import "ASIHTTPRequest.h"// 使用下面的方法替换原方法
- (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];    
}

以上操作都是把下载放在了后台,然后当下载完成再进行处理并显示。

运行一下看看,点击“Grab”按钮,它将迅速跳转到下一页,而不是有一个超长时间的暂停。但是,还是有一个大问题:

Update a row in a UITableView when image loads


下载完了以后居然tableview不显示图片?!你可以通过上下滑动让图片出来,但是这也太“专业”了...怎么弄好?

使用 NSNotifications!

这个方法很容易,在获得一个图片对象ImageInfo的实例以后向tableview的类发送通知,让tableview刷新界面。

开始吧!打开ImageInfo.m ,做如下修改:

// 添加在 getImage 方法里,就在 image = [[UIImage alloc] initWithData:data]; 这段代码之后
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.razeware.imagegrabber.imageupdated" object:self];

如果图片下载好了,我们会发送通知并且将新的 ImageInfo 实例发送给tableview,然后tableview更新一下就好了。

然后我们进入:ImageListViewController.m 然后做如下修改:

// 添加在 viewDidLoad 方法最后
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageUpdated:) name:@"com.razeware.imagegrabber.imageupdated" object:nil];// 添加在 viewDidUnload 方法最后
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.razeware.imagegrabber.imageupdated" object:nil];// 添加一个新方法
- (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];}

OK,然后运行一下,你会看到图片出现了!

Asynchronous image loading

Grand Central Dispath 和 Dispath Queues, 卧槽...

这俩东西才是这次的重点,GCD 和 GCDQueue 。

现在你的app还有问题,如果你点击了“Grab”!然后一直往下拼命的滑动tableview,你会发现,界面好像卡住了呦...这是因为它还在保存和解压缩压缩文件。这是因为ASIHttpRequest 在完成的时候调用的方法是在主线程里面运行的。如下:

[request setCompletionBlock:^{NSLog(@"Zip file downloaded.");NSData *data = [request responseData];[self processZip:data sourceURL:sourceURL]; // 卧槽 - 又特么跑主线程去了!
}];

那么,我们是否可以将这样的任务在后台悄悄的运行?

于是,在iOS3.2以后,苹果发布了一个简单有效的方法来做这个,那就是通过 Grand Central Dispath 系统。基本上你无论什么时候想要在后台运行都可以将代码放在 dispath_async 中运行。

GCD 会帮你完成所有的细节,如果需要的话:创建一个新的线程,或者重用一个已存在的可用线程。

当你调用 dispath_async ,你会把它传递给一个 dispath queue ,你可以把它想象成一个存储了所有你传进去的代码块的队列,当然,先进来的代码块先运行。

你可以创建自己的 dispath 队列, (via dispath_creat),或者你甚至可以从主线程获得一个特殊的 dispath 队列(via dispath_get_main_queue)。在这里我们将要创建一个名叫“backgroundQueue”的线程来完成我们想要完成的任务。

Dispath Queues, 线程锁, 和猫粮

一个dispath队列是串行的,这代表在同一时间放入队列的代码块只会运行一部分,这很方便,因为你需要来保护一些共享的数据。

如果你对多线程的线程锁不熟悉,可以想想前面的猫的例子,如果你所有的猫在同一时间想要吃同一盘猫粮,那会怎么样?!这就是线程锁的作用。让你的猫排成一队来吃,就简单多了。

Maybe GCD really stands for Grand Cat Dispatch?

或许GCD 代表的时 Grand Cat Dispath???

这个想法最开始就是想要使用dispath queues 来保护数据。使用dispath queues只运行一段代码这保证了在同一时间只有一段代码对你的数据进行操作。

在这个app中,我们需要保护两个类型的数据:

1、在ImageListViewController中的linkURLs 数组。为了保护这个,我们将其代码结构化这样保证这个数组只会在主线程中被接触到。

2、在ImageManager中未处理的压缩文件。为了保护这个,我们将其代码结构化这样保证这些数据只会在“backgroundQueue”中被接触到。

开始使用GCD吧!

从 ImageGrabber.h 开始 ,做如下改变:

//  添加到文件的头部
#import <dispatch/dispatch.h>// 添加新的变量
dispatch_queue_t backgroundQueue;

要使用GCD ,首先要import 它,然后声明一个 dispath queue,这样我们准备好要开始运行我们的背后动作啦!

下一个,打开ImageGrabber.m 然后做如下修改

// 1)  添加在 initWithHTML:delegate 的最下面一行
backgroundQueue = dispatch_queue_create("com.razeware.imagegrabber.bgqueue", NULL);        // 2) 添加在 dealloc 的最上面一行
dispatch_release(backgroundQueue);// 3) 修改 process 方法,如下
- (void)process {    dispatch_async(backgroundQueue, ^(void) {[self processHtml];});    
}// 4) 修改  retrieveZip 内部调用 <span style="font-family: Arial, Helvetica, sans-serif;"> processZip 这个方法的代码如下</span>
dispatch_async(backgroundQueue, ^(void) {[self processZip:data sourceURL:sourceURL];
});// 5) 修改 <span style="font-family: Arial, Helvetica, sans-serif;">processHTML **和** </span><span style="font-family: Arial, Helvetica, sans-serif;">processZip 最后的代理,如下</span>
dispatch_async(dispatch_get_main_queue(), ^(void) {[delegate imageInfosAvailable:imageInfos done:(pendingZips==0)];
});

这些都是简单,但是非常重要的调用,因此我们来讨论一下每一步

1、创建了 dispatch queue,当你创建它的时候需要给它一个名字,名字一般都是你的bundleid + queue名,比如例子中的,我们前面说了要创建一个backgroundQueue。

2、当你创建了一个dispath queue,不要忘记释放它!在这里,我们在ImageManager 被释放的时候释放它。

3、旧的process方法只是直接的调用 processHtml 方法,因此它会运行在主线程上,并且阻塞了界面的展现,知道HTML被解析完毕。现在我们简单的使用 dispatch_async 创建一个backgroundQueue 然后让这个方法在线程中运行!

4、同样的,我们在下载完数据以后在ASIHTTPRequest中只是简单的告诉主线程,下载完毕。不同于一直阻塞界面等待数据保存完全,现在我们将它放在backgroundQueue中。当然也要确定pendingZips变量是在被保护的状态下的。

5、我们想要确定我们在调用代理方法的时候是在主线程运行的。首先,根据我们上面所说的,确定 linkURLs 数组在视图控制器中只通过主线程被访问。其次是因为这个方法与 UIKit 的子类相互作用,而 UIKit 的子类是必须在主线程中调用的。

就是这样啦!编译,然后运行你的代码,你会发现,现在它流畅多了!



但是!还有一点!

如果你编程过iOS一段时间,你应该听过 NSOperations 和 operation queues。你会不会在使用的时候不知道到底使用operation 还是GCD ?

其实呢,NSOperations 就是简单的在GCD之上的一个封装,所以当你使用了NSOperations 的时候,你其实还是在用GCD。

只不过是NSOperations 给了你一些比较不错的特征,这也许是你喜欢的。你可以根据其他的operations 来创建一些 operations 或者重新对你提交好的operation queue 进行排序,类似这样的操作。

事实上,ImageGrabber已经用了NSOperations了!ASIHTTPRequest 在其底层就在使用这个东西,你也可以自己配置 operation queue。

但是,我们到底该用哪个呢?就像这篇文章中的app,我们只是在简单的逻辑中用到了简单的线程,所以我们直接使用GCD,因为我们不需要NSOperations 的那些新的特性。但是如果你真的要用到这两个,一定要自由的使用,不要为了用而用,反而不好。

点击这里 下载这篇文章中所提到的APP到最后的完整代码。

后面还会有对GCD 更深层次的详细介绍,各位记得关注哦!






http://chatgpt.dhexx.cn/article/p7tuFURE.shtml

相关文章

iOS【开发热门游戏_超级猜图Demo】

先看效果图 思路 需求分析 1&#xff0c;搭建界面 1》上半部分&#xff0c;固定的&#xff0c;用Storyboard直接连线(OK) 2》下半部分&#xff0c;根据题目的变化&#xff0c;不断变化和调整&#xff0c;用代码方式实现比较合适(OK) *备选按钮区域(OK) *答案按钮区域(OK)…

unity骚操作之: 解决AVPro Video在安卓移动端播放不了SteramAssecting里面的视频 或者 URL视频

Unity自带VideoPlay用来播放视频有bug,在移动端测试无法正常使用Url播放,所以找到AVProVideo,这个插件很好用,移动端亲测可用,功能也全,这里记录下使用心得 下载地址:支持Unity2019及以上版本AVProVideo1.11.5视频播放插件.zip_avpro安卓11不能播放-C#文档类资源-CSDN下…

unity普通操作:animator播放,暂停,判断播放完成【(增加1个判断如果这个是动画融合的情况)】

1.播放&#xff0c;暂停 anim.CrossFade("s2", 0); anim.speed0&#xff1b;//暂停 anim.speed1&#xff1b;//播放 判断播放完成&#xff08;Update方式&#xff09; void Update(){AnimatorStateInfo stateinfo2 anim.GetCurrentAnimatorStateInfo(0);//判断…

猫猫学IOS(五)UI之360等下载管理器九宫格UI

猫猫分享&#xff0c;必须精品 素材下载地址&#xff1a;http://blog.csdn.net/u013357243/article/details/44486651 先看效果 主要是完成了九宫格UI的搭建 代码 - (void)viewDidLoad {[super viewDidLoad]; //九宫格中每个格子的宽 #define kAppViewW 80 //九宫格中每个格…

猫猫学IOS(七)UI之UITextField代理事件_类似QQ登陆窗口的简单实现

猫猫分享&#xff0c;必须精品 素材代码地址: http://blog.csdn.net/u013357243/article/details/44587005 原文地址&#xff1a;http://blog.csdn.net/u013357243/article/details/44571163 先看效果图&#xff1a; 学习代码 // // NYViewController.m // 05-UITextFie…

国行Apple Watch 开启 ECG 心电图功能

国行Apple Watch 开启 ECG 心电图功能 设备要求 1.iphone 升级到最新系统 2.apple watch 升级到最新系统 3.最好有一台备用iphone来保证数据不被泄露 开通原理 1.我们需要有一个已开通ECG功能的appleId 2.登录此appleId到设备 3.同步健康App数据到本地 4.退出账号 5.登…

iOS开发:面向协议编程与 Cocoa 的邂逅 (上)

//联系人:石虎 QQ: 1224614774昵称:嗡嘛呢叭咪哄 喵神原文地址&#xff1a;https://onevcat.com/2016/11/pop-cocoa-1/ 本文是笔者在 MDCC 16 (移动开发者大会) 上 iOS 专场中的主题演讲的文字整理。您可以在这里找到演讲使用的 Keynote&#xff0c;部分示例代码可以在 MDCC …

猫猫学IOS(十三)UI之UITableView学习(下)汽车名牌带右侧索引

猫猫分享&#xff0c;必须精品 素材代码地址&#xff1a;http://blog.csdn.net/u013357243/article/details/44727225 原文地址&#xff1a;http://blog.csdn.net/u013357243?viewmodecontents 先看效果图 代码 ViewController //ps&#xff1a;新建iOS交流学习群&#xf…

在android上模拟ios阴影效果

update一下&#xff0c;下面方法现在来看很low&#xff0c;其实最简单的是直接自定义一个drawable android上大部分时候阴影是不符合产品需求的&#xff0c;就比如我们就要求实现一个类似ios的圆形图片的阴影??? cardview阴影就挺好&#xff0c;可是他喵了个咪的&#xff0…

猫猫学IOS(六)UI之iOS热门游戏_超级猜图

猫猫分享&#xff0c;必须精品 素材地址:http://blog.csdn.net/u013357243/article/details/44539069 原创文章&#xff0c;欢迎转载。转载请注明&#xff1a;翟乃玉的博客 地址&#xff1a;http://blog.csdn.net/u013357243?viewmodecontents 先看效果图 思路 需求分析…

iOS汇编基础(二)寄存器

以arm64为例 xcode调试汇编1. xcode 查看运行时的汇编代码 debug -> debug workflow -> always show disassembly 2. Xcode改变pc值 register write pc 0x1005d6928 3. 单步运行一步汇编代码:ni 4. 读取某个寄存器 (lldb) register read x0x0 = 0x0000000000000000…

Unity简单操作: DoTween的onCom..回调函数里面执行错误 不返回哪条函数出错的解决方案,与iOS平台为什么需要勾选安全模式

目录 DoTween的onCom..回调函数里面执行错误 不返回哪条函数出错的解决方案 当然 在iOS平台 测试好了的话 需要勾选它&#xff0c;不然iOS机制原因 会导致onComxx回调 没有执行&#xff01; DoTween的onCom..回调函数里面执行错误 不返回哪条函数出错的解决方案 如下图&…

iOS汇编基础(一)

一 高级语言运行过程 二 汇编语言的特点 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能能够不受编译器的限制,对生成的二进制代码进行完全的控制目标代码简短,占用内存少,执行速度快汇编指令是机器指令的助记符,同机器指令一一对应。每一…

STM32cubIDE 黑色主题_主题 | 喵咪旅行日志 VX可爱系列主题 BySasa

今天带来一款Sasa小宝贝儿投稿的可爱系列VX主题&#xff0c;应该也算是可爱系列里偏向简单的了。 鉴于前几次留言总是碰到隔着网线就不需要情商的DS。在前边先申明清楚吧&#xff0c;审美各有差异&#xff0c;不喜欢不用就好了。如果没有素质去diss投稿者作品&#xff0c;那就别…

tp框架怎么连接mysql_tp框架知识 之(链接数据库和操作数据)

框架有时会用到数据库的内容&#xff0c;在"ThinkPhp框架知识"的那篇随笔中提到过&#xff0c;现在这篇随笔详细的描述下。 一、链接数据库 (1)找到模块文件夹中的Conf文件夹&#xff0c;然后进行编写config.php文件 我这里是这样的文件路径 (2)打开这个config.php文…

thinkphp6开发cms项目之安装tp框架

1.安装thinkphp6框架&#xff1a; composer create-project topthink/think tp需要安装的扩展&#xff1a; composer require topthink/think-multi-app //多应用 composer require topthink/think-view //视图 composer require topthink/think-captcha //验证码2.如果运行ph…

我的服务器开发之路-安装thinkphp

http://www.thinkphp.cn/down/framework.html 下载thinkphp 我这边下载的是thinkphp5.0.3核心版 然后&#xff0c;打开xftp 4&#xff0c;将下载的thinkphp5.0.3的压缩包解压到tp503目录&#xff0c;并上传到/data/www/web目录 然后&#xff0c;打开浏览器&#xff0c;输入 域…

tp框架与mysql_TP框架对数据库的基本操作

数据库的操作&#xff0c;无疑就是连接数据库&#xff0c;然后对数据库中的表进行各种查询&#xff0c;然后就是对数据的增删改的操作&#xff0c;一步步的讲述一下框架对数据库的操作 想要操作数据库&#xff0c;第一步必然是要&#xff1a;链接数据库 一、链接数据库 (1)找到…

php tp框架,TP框架

tp:thinkphp框架&#xff0c;它也是一个轻量级的框架&#xff0c;它有中文社区&#xff0c;中文的帮助文档。它是国人开发的框架。 Thinkphp框架最初是由于企业级网站的开发和web网站的开发诞生的&#xff0c;最初诞生在2006年&#xff0c;它叫fsc,2007年正式更名为thinkphp,它…

TP5框架

第一次写博客 因为自己的技术是在有点差,所以想提升一下自己的技术,所有尝试写下博客,这次是关于TP5框架的, 开发PHP肯定要环境,手搭PHP不是不行,只是切换版本的时候特别麻烦,所以我下载了一个集成环境,用的是PHPstudy,小伙伴们可以自行去下载 ###我下载的是TP5.0完整版本,压…