iOS开发之异步回调的有趣玩法

        最近在开发过程中总结了一些关于异步回调比较有意思的用法,给大家分享一下。不是什么高级东西,iOS老司机们看完可以给小弟指点指点,但是也不适合新手看,如果你还不太熟悉OC中的block或者Swift中的逃逸闭包,那么这篇分享你看着可能会很迷糊。文中代码部分使用Swift,OC可以照搬,思路是一样的。

        在iOS开发中,block或者闭包(后面我就统称为闭包了,毕竟我已经投入了Swift的怀抱)可以说无处不在,在Swift中我们使用闭包可以用来做数组排序类似的功能,把元素大小比较的实现通过闭包的形式抛出来。但更多时候,我们用闭包来实现异步回调,因此我们经常要跟逃逸闭包打交道。


        举一个很简单的例子,图片下载,大家可能最熟悉的框架就是SDWebImageDownloader,大家经常会在tableViewCell里去异步下载图片,下载完成后在回调里把图片显示出来。

获取图片的简易实现


Cell的简易实现

        这个过程看似很简单,无非就是先找本地有没有图片,有就直接回调,没有就创建一个http请求去下载,下载完成后回调,当然你就这样去实现,没有错,而且应用能正常运行,大部分时候也不会有什么问题。但是我们来细想一下这个过程到底有没有毛病。

        获取图片的回调,可能是同步的(本地有,直接回调),也可能是异步的(本地没有,要去下载),同步的当然是不会出任何问题的,但是异步的就不一定了。

        既然图片本地没有要去下载,那么就有可能有好几个cell需要的图片下载地址一样,或者cell被重用后又下载一次,不做任何处理的话,那么同一张图片可能我们得下载好几次,这样就浪费流量浪费空间了。不过好在像SD这样的第三方库,已经为你做了防止重复下载的事情,只要你下载的地址相同,就只会下载一次,并且不会漏掉你的任何一个回调,你可以放心的使用。

        但是,我们还可以做得更极致,你有没有考虑过,网络不好的时候,一张图片要下半分钟的情况?当用户打开你的应用,这么多的图片在转圈,半天都停不下来,是不是难免会暴躁,然后明明网速就很慢下图要下半天还非要手痒痒去上下滑一下你的tabletView,滑一下还不解气,来回滑个几十下,差点没把手机给摔了。

        这个时候tableViewCell的重用机制就把你带坑里去了,当你把一个cell滑出屏幕范围再滑回屏幕范围的时候,cell又去执行了一次获取图片的方法,图片还是没下载完,于是乎这个cell又创建了一个回调,当你来回滑了几十下后,一个cell可能就创建了几十个回调等待下载图片完成后好执行。

      然后突然一下你的网速恢复了,那些个图片一下子就下好了,cell的几十次回调就能执行了,你不觉得很浪费性能吗?我一个cell明明就显示一张图,就因为网速慢加上我手痒滑来滑去的就得执行几十次绘制图片。而且你怎么知道你哪张图片先下完?除非下载图片用的串行队列,不然很可能你的cell最终显示的图片不是它应该显示的图片。当然你可以在回调的参数中多加一个url,回调的时候判断一下当前需要显示的url和回调中的url是不是相等,如果不相等说明这个回调是cell重用之前创建的,直接忽略掉就行了。这种方法也可以解决问题,但是我还想看看闭包可以怎么玩,从新手开始接手项目,踩过太多异步回调的坑了,下面来说说我最近想到的新的解决办法(其实我也还是个萌新),放心,绝对简单到轻描淡写。


        第一步,我们要解决异步任务重复的问题,有人要问了,SD已经有这个机制了啊,废话,我用SD的库我第二步怎么实现,而且自己实现一下也是有好处的嘛,毕竟这个真的不难啊。

稍稍改变下代码,代码不好截图,分两部分截。

第一部分
第二部分

        如果图片本地不存在,那么就需要下载,下载完成后就需要异步回调,我们可以把url保存在一个集合里表示这个url地址下的图片正在下载,把回调已可变数组的形式保存在一个字典里,以url作为key。每当又有一次下载该url的任务,就可以不用再下载一次,只需要把回调添加到该url对应的回调数组中就行了。当下载完成时,根据url找到对应的回调数组遍历执行一次就可以了。是不是很简单,对啊,就是这个简单啊,没有什么高大上的技术。


        第二步,我们要解决cell重用的问题,也就是回调重复多余的问题,这个问题理解起来有点困难,需要你对block或者逃逸闭包很熟悉。

        很多时候我们只知道调用一个函数,定义好回调需要执行什么任务,就什么都不管了,等着函数去调用我定义好的回调任务就行了,很少去关注闭包在函数本身中是怎么执行的,包括闭包的生命周期是怎么样的。

        对于Swift来说,涉及到异步执行的闭包必须声明为逃逸闭包。普通闭包只能同步执行,他们的生命周期不一样(OC中的block不做这样的区分,可以统一看做逃逸闭包)。普通闭包的生命周期随着函数结束而结束,也就是说你没法写一个异步任务去执行普通闭包,也没法把普通闭包保存到其他地方去想什么时候执行就什么时候执行,说形象点,普通闭包像是一个被剥夺了自由的奴隶,它没法从函数的生命周期中逃逸出来。而逃逸闭包就像是一个上流社会的达官贵人,想去哪里就去哪里,只有当没有任何一个地方需要他的时候,他的生命周期才结束,所以你可以把逃逸闭包保存起来看情况使用。

        逃逸闭包有一个缺点就是,如果我把它保存在一个数组中,我就再也不能精确地定位到它,更无法准确地删除它。它像是一个对象又不完全是一个对象,它天生的值拷贝属性加上无法进行哈希匹配,导致我们无法在数组中对其进行定位,最简单的办法就是把逃逸闭包封装在一个类中,通过对象去定位逃逸闭包。

        回到刚刚那个问题,当cell消失后等待被重用时,怎么把它创建的逃逸闭包给释放掉?在第一步中,我把逃逸闭包以可变数组的形式保存在字典中,上面我们也分析了,我们没法在数组中去定位一个逃逸闭包把它删掉,因此,我们需要定义一个类,把逃逸闭包封装起来,再存在数组里,这样我们就可以释放指定的逃逸闭包了。最终的代码就是下面这个样子。

第一部分
第二部分



        总结,很多语言都支持闭包这种表达式,但是OC或者Swift的闭包应该是目前所有语言的闭包实现中最好用的,你可以在闭包中随意操作上下文变量,而不需要去担心上下文变量的生命周期,你只需要注意循环引用的问题就行了,而很多其他语言的闭包是不能捕获上下文变量的。闭包在异步任务中使用非常广泛,你可以把闭包保存起来,这感觉就像我把一串代码实现给保存了起来,待异步任务完成时看情况去调用或者释放闭包,如果你需要定位某一个保存起来的闭包,你需要把闭包封装在类中使用。

        第一次写东西,瞎写的,有错误的地方望指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 序 本文主要解析一下apache common pools下的GenericObjectPool的参数设置 Gen...
    go4it阅读 4,932评论 0 0
  • 为了向The ABCs of Death致敬,我打算写26个小故事,故事与字母无关,仅作计数。 ---------...
    九大人阅读 232评论 0 0
  • 它长势开始歪的时候 身旁的杨柳树恰好开出了初春的第一朵小花 它的脖子探入春江水暖的时候 身边的樱桃树恰好悄悄落下第...
    却悔阅读 560评论 2 0
  • 阿福童---最开始接触这个名词是在2015年3月的某一天。李校长通知我们十几个骨干班主任调出两天的课程做一个关于阿...
    蕾蕾lcm阅读 320评论 0 0