看了下离开上次写简书博客的日子,都有各自适合的盛放液体公海赌船网站

摘自网易音乐:温澜潮生

看了下离开上次写简书博客的时刻,已经仙逝了多个多月了,很惭愧。正好如今项目不忙,抽点时间探讨下第三方库,朋友指出总括写成博客就这样开篇了。内容篇幅会比较长,所以希望各位看官搬好小板凳看SDWebImage源码解析,假诺没有定性真的是很难坚定不移下去。希望我们可以百折不挠跟着博主一块学完SDWebImage源码体系。

相对光年.jpeg

冬日暖阳略显疲态。

一.预备知识

在规范学习源码前,先讲一些SDWebImage中用到的生僻知识点,有些用的很频繁,可是过多个人对这个知识点模糊不清,假诺不搞清楚会大大影响阅读功用,比如枚举NS_OPTIONS的二进制位运算。

第十三章(+):鄂尔多斯

我在淮安火车站相对续续地睡了一夜,半夜列车到站,阵阵轰鸣声把自己吵醒,当人流像潮水般退去,苏醒平静的时候,我才可以再一次入睡。
本人没有带眼罩耳塞睡觉,在火车站里,我或者时刻警惕着。
半夜醒过来的时候,我发现姜来人不见了,只剩下行李在自身身边,我心头咯噔了一下。
以至我见状他迷迷糊糊地从厕所里面走出来,我才松一口气。
人连连会变的,而且变得特别快,在前不久,我还在想怎么摆脱姜来,然则现在,我却突然担心她会不辞而别。

好像深夜,人越发多,睡眠被堵塞得特此外频繁,我简直不睡了。
自己拿着洗漱用品在更衣室里草草地清洁,冷水洗过脸之后,整个人都焕发爽快。
本人买了两瓶咖啡回去座位上,姜来也醒过来。刚睡醒的她,看上去呆呆的,我递给他一瓶咖啡,他拿在手上,咕噜咕噜地全喝光。
“现在几点了。”他带着一嘴咖啡味问我。
“还早吗,现在7点不到。”我抬头看着挂在车站里的大钟表,慵懒地说。
“我们几点的列车。”
“10点35分。”
“我的天,还要等这么久。我真后悔,明儿早上为什么不去住急速商旅呢?这一宿我都没睡好,现在一身特别难受。”他站起来,伸了个懒腰。
“后悔也晚了。你去洗漱一下,精神爽快点。”
“好啊。”他无奈地说。

深夜7点过后,火车站挤满了人,行李堆拿到处都是,我带上动圈耳机,看着电子书,对这一个世界不闻不问。
姜来继续看她的《在半路》,不明白,他尾随着书里的栋梁之材去到什么样地点了。
本人身边的位子来回换了少数波人,他们坐下来,吃着喝着说着笑着,然后又坐上列车,在车上继续吃着喝着说着笑着,到此外一座城市,过着同样的生存。
到头来熬到了10点35分,大家登上了K1278次列车,下一站是河北乐山。
刚上车,姜来对自身说:“大家本次来内蒙古,都不曾亲自踏上这里的草野,有点可惜。”
我说:“没什么可惜的,大家一路上,能来看更多的草地,去内蒙古的草地,最好的法门,是自驾,我们都不会开车,难道在草原上徒步吗?”
“大家不可以骑马吗?”姜来问。
“你会骑马吗?”我问。
“不会。”
“这你仍旧别考虑了。”

当我们的列车缓缓往复旦去,我问姜来对海南有什么样概念不?他说,不清楚,应该满地都是煤矿吧。
自身说,你错了,这不叫煤矿,那叫金矿,挖出来的,都是钱。
这句话,其实不是自身说的,这是自身一个海南的好情人——小于,告诉自己的。
他家做煤矿生意,他们管煤矿叫黑金。他告知自己,刻钟候,几乎每一户安徽人家,都有妻儿和亲戚,直接或直接地从事煤矿生意。
而他家,更加疯狂,父辈整个家族,从老到少,都专心地投入到煤矿事业的建设,按照现行的术语,应该叫煤矿产业链全覆盖,从挖煤,煤矿器械,到煤矿运输,甚至是煤矿提炼,都在他家族的掩盖下,蒸蒸日上,红红火火。
他家里所有人都对煤矿有着类似神经质的洋洋得意。
在江苏从事煤矿行业的人,最恐怖的就是遇上矿难,也就是所谓的见红,所以,吉祥的革命,在她的家门,并不讨喜,他说她们家,一直不穿黑色的行头。
扭转,也得以设想,他的家门,最欣赏的水彩,肯定是绿色。
她跟自己说,别人家过下元节的时候,挂的都是绿色的春联,唯独他们家族,用的是黑色的纸,再配上鲜亮的金漆字。
自身一筹莫展想像褐色的春联挂在家门口的榜样,但自身能设想,要是世界一片漆黑,这该多可怕。

仅次于是家门内部,唯一没有从业煤矿事业的人,他为了逃离家里人的牢笼,高三报志愿的时候,他挑选了遥遥无期的黑龙江岛,毕业后,他选拔到了法国巴黎办事,从此,和煤矿脱离。
这几年,国家积极提高清洁能源,对煤矿举办控产减产,小于家里逐步从辉煌走向没落,煤矿关了一个又一个。
低于很幸运地逃离了四川,不过,这么些和煤矿相依为命的人,则尚未那么幸运,他们不得不无力地,和煤矿一起没落,就像这个,曾经埋藏过众多尸体的,深不见底的煤矿坑。
如此这般的故事,在河南遍地都是,就像漂浮在江西氛围中的煤屑粉,每一口的透气,都带着淋淋的鲜血。
这就是我对甘肃,简单而强行的印象。

俺们的火车,从平原草地逐步驶进高原地区,植被从草甸向常绿阔叶林渐变,人烟和树林一并茂盛。
从包头到张家口,只需坐4个钟头的列车,在车上小息一会就到达了。

鄂尔多斯到底是个小城市,火车站等候的人也不多,诺大的广场很冷静。
早上的阳光把火车站广场照得滚烫,通化引人注目比江门要炎热,大家出站之后,没走多少路程就起始冒汗,这跟大家在九江的体感完全不雷同。
是因为火车站广场附近在道路维修,我们要乘坐的公交得要走很长的一段路。
我们背着登山包,在一条无人穿行的道路上行走,路两旁都是些小型事业单位和小个体户的门店,店主大多在休养生息,少数会三三两两聚众在一块打牌,看到我们走过,也不会多看一眼。
网上里说辽宁的GDP已经是全国倒数几名,看来也不是什么虚假音讯。
从火车站到我们住的青旅,其实不远,大家上了公交之后,姜来问我:“你规定这家青旅不是黑店吗?”
本身不了然怎么回复这么些题目,一朝被蛇咬,十年怕草绳,然则除了青旅,没另外选用。
既来之则安之,我对姜来说:“我在大网站预约的,应该不会有问题,我看看它家网上评论蛮高,尽管不是很便利,但起码安全有保险。”
“我也就顺口一问,反正大不断我们就住急速宾馆。”姜来说。

到了青旅,一切正常,总经理娘是个很年轻的中年妇女,接待我们的时候很平易近人。青旅里,住客很多,六世间里,几乎住满人。这家应该是家正常的青旅,我想。
姜来依旧睡在自我的上铺,他开始爱上了上铺,他说,在上头,空气好。
屋子不大,三张左右铺床占据了绝大多数的空间,窗户外面就是承德古都的步行街,隔着玻璃也能听见外面商户循环播放的优惠广告,就像在洗脑似。
屋子内部唯有我们五个,其他的人都出去玩了。
看日子还早,我把姜来从上铺拽下来,带他到青旅旁边的千年古刹——华严寺。

我的学童证从来派上用场,姜来每一回观察我用假学生证都会给自己翻白眼,他会说这样大的人还用学生证真不要脸。
要不要脸都是协理,最重大的是,我能省下一大笔钱。
在入口处,工作人员让我出示学生证,姜来认为我即将被罚,幸灾乐祸地说:“这年头,长这么老的学士也是名贵一见。”
我笑着给验票员出示学生证,她看了我一眼,笑着说:“不老不老,这小伙子长得水灵灵的,走吧,进去吧。”
走进去寺里面之后,姜来絮絮叨叨地说:“这一个老大妈肯定是老眼昏花,都28岁的人,还水灵灵的,大姨对您有意思啊,谢已,要不您就从了二姑,在黑龙江好好吃饭吗,说不定三姑家是煤矿主,你之后有福了……”

华严寺历经千年的风霜,集结了三朝的出色,在辽代的地基上,金朝秀美的建筑挽手相连,殿院成林,金朝留下来庄重佛塑和牌匾,在静谧的殿里生生不息,寺庙经过一代又一代的人的修缮,依旧保持着佛性。
堪称国宝的重楼式壁藏就在薄伽教藏殿里。
全国孤例的重楼式壁藏,上层为佛龛,下层为藏经柜,为了采光和通风,殿后开了一小窗,窗中间有一座小木拱桥左右老是,五间天宫楼阁从左到右分布住着不同的神佛,这是自家一贯没见过的殿中殿。
在殿里,还有三十一尊来自辽代的皇室彩塑,每一尊的神色和外貌都各不相同,身上的飘袖和衣带自然唯美,婉丽动人。他们身上的彩漆已经颓落,裸表露自然的纹理,镇守着华严寺已经重重个新春,看到他们,就相当于观察了历史。
出于历史原因,寺庙成了江山重点珍惜文物,为了响应政坛的召唤,不许烧香也不设功德箱,寺中所有的香炉都被大铁板盖上,没有了效能性的香炉成为纯粹的模式装置。
姜来想烧香拜佛也未曾主意了。我觉得这也蛮好的,除了让寺庙更佳干净清爽,更能让佛法显得纯粹,所谓的烧香拜佛,然而是基于欲望之下的吹捧。
寺庙里还有一座华严宝塔,登塔需要穿上鞋套,大家套上彻底的鞋套,小心翼翼地走进塔里。
在塔底的地宫,有云冈石窟创制者昙曜的舍利子,姜来隔着一层厚厚的玻璃,仔细地搜索舍利的身影,所谓的舍利子,其实也就是一小块白骨头。
供奉舍利子的金塔花纹繁复,华丽极致,围绕在地宫四周的,还有为数不少个小佛像,他们在四方看着我们,佛法全写在他们的眼里。
塔里的楼梯至极陡峭,我们很小心地往上爬,宝塔总共有五层,其中,三层明,两层暗,明层能走到塔外,暗层则不可能,寓意着人生明暗有常。
大家总算终于通过了拥堵的阶梯抵达宝塔顶层,安顺古城的风貌尽收眼底。
我们围绕着宝塔走了一圈,在一个小角落里,还看到基督教堂就在华严寺的跟前,寺庙和教堂看上去关系友好,看来佛祖和基督,应该是三个好爱人。
通过在线地图,我还发现,在大理古都里面,还有道教,伊斯兰教和天主教,小小的一座古城,居然容纳了五大教派,那里也算得上是宗教共和国了。
早晨的凉风习习,姜来说他在佛祖面前许了个愿,我问她许了如何愿,他说无法告诉我,不然就不灵了。
我说自家从没许愿。
她问我干吗。
“三岁这年,父母离婚,之后的每一年诞辰,每趟去寺庙祭祀,我只许一个心愿,我不贪心,我只愿意爸妈能重归于好,就这样简单。这个愿望一贯陪伴我到7岁,那一年,我爸娶了新的夫人。从这将来,我再也无法愿了。”我说。
“你是不再相信心愿的能力吧?”姜来问我。
“我不信任。这都是自欺欺人。”
“可自己深信。”姜来的眸子里倒影着夕阳,他一眨眼,这一个世界就毁灭了两回,他一睁眼,这一个世界又重建五遍。
“你驾驭我刚才许了个什么愿吗?我求佛祖保佑,大家终将能不负众望环游中国。”姜来持续说。
“我刚不是跟你说过呢?愿望说出来就不灵了。”我说。
“我才不担心呢,我领悟大家必然能打响。”姜来信誓旦旦地说,他总能冒出意外的自信。
“我这辈子许的愿望都没有一个贯彻,你说,我要不要扭转,许一个不容许实现的心愿吗?说不定,就着实能兑现呢。”我说。
“你现在还有什么样意思?”姜来问。
“我愿意大家无法不负众望环游中国。”我笑着说。
“什么狗屁愿望,呸呸呸。”姜来向天空呸了几下,不领悟口水会不会滴落到塔下路人的随身。
“这叫负负得正,懂不懂。”
“懒得跟你扯,走啊,大家该出来了。我要上城墙看落日!”说完,姜来急忙地钻进窄小的梯子,我尽快跟上去。

马岳阳古都不是很大,也不算小,我们从华严寺出来,不到半钟头就走到南门城墙。
半路我们还经过了中华最古老的九龙壁,我们抱着老大愿意走进来,却只发现除了一块九龙壁之外,啥也未尝,络绎不断的旅行团把小小的院子占满,我们呆了不到五分钟就走出去。
跟我们共同走出去的,还有另外一个黄毛丫头,她盯着姜来,上下打量了一番,脸上一脸惊呆的神气,我觉着他想要说怎么,结果,她转身就走了。
姜来认为这几个女子莫名其妙的。
“也许人家对你有意思,女子总是害羞的。”我说。
“我这厮,没什么意思,而且自己对他一些趣味都不曾。”姜来头也不回,继续往城墙走。

咱俩从南门登上城墙。
南城墙的瓮城,是四面城墙中最大的,里里外外一共三重,翁城外,还有半月模样的月城,重重叠障,拒人千里。
在和平年代,城墙只享有历史的意思,御敌防守的效劳已经丧失,即使如此,新修的龙岩古都墙,仍旧保留着来自时光的美。
上了城墙,每走几十米就有一座箭楼,箭楼上的灯笼,随着黄昏的光顾,也逐渐点亮。
城墙很长,总共有7.24公里,若是走路绕一圈,我以为我会死的。
当大家发现城墙上可以租自行车的时候,大家提神得跳起来。
在城墙上骑自行车,这纯属是聪明绝顶的主意。
可是,当大家上了车,在崎岖不平的路面上骑行时,我才意识,那未必是一个好的呼吁,没骑多少路程,我的臀部就受不了。
姜来也一样,只是,他比自己聪明,他脚站在车子的踏板上,半坐着,他说,只要屁股不碰坐垫,那就不会受罪。
当大家骑了大体上,我看来迎面而来一个黄毛丫头,骑法和姜来一摸一样,看来她的臀部也很难过。
他看了俺们一眼,转眼就从我们的身边穿过,在斜阳下,大家目光短兵相接,我认出她,是刚刚在九龙壁遇见的丫头。
姜来只顾着骑车,都不曾留意到她,我跟她说起这事的时候,他说:“你以为内江很大啊?丹东就如此点大,再一次相遇也很正常。”
可自己老是认为,这妮子的眼神有点尴尬,但是,何地有问题,我又说不出来。

因为臀部疼,我只好在姜来身后逐步骑着。
从南往北,两边的风物各不相同,西边的古城,颓废,残旧,阴沉,是旧时代的象征;东边的新城,热闹,新颖,活泼,象征着新世界。
五个世界当中,高耸的城墙像是一道棕色屏障,或者说,一条斑驳的点子——它将这六个世界相互维系,相互牵连,这是一种,类似相互寄生的涉嫌,他们谁也缺不了何人。
天黄海北地望去,西边的高山上有整齐的风力发电机在运作,他们每转动一圈,阳光就被搅碎成小块,落在地上的时候,已经成了金藏粉色的散装,有些会被风吹走,有些会被人捡走,大部分则会默默地死去,这是他们轮回了许多遍的运气。
日光下山之后,城墙上的城楼,角楼,箭楼,统统亮起了黄彤彤的灯光,影子先导在砖墙上怒放。
我们骑到了北门,发现翁城的城墙上悬挂着一个重型的版画,是一个裸体的胖子,灯光打在身上,浑身发白,姜来见见将来哈哈哈大笑,他觉得这多少个水墨画其实是太搞笑了。
自身以学术的角度分析,我认为这是以超现实的措施对历史举办奚落,不温不火,“圆润”且适合。
姜来说听不懂。我把这话翻译成“粤语”告诉她:
“这就是瞎鸡吧扯蛋。”

骑到西北角,正在翻修的城墙还未曾并轨,不可能持续前行,我们回头重临,还了自行车,从东门走下城墙。
夜间,城墙上还有乘客在游历,二姨大爷在东门的广场放着音乐,整齐地跳着广场舞。
我想起了在科隆韩江旁边看到的广场舞,准确地说,也不能够叫广场舞,因为她俩可不是固定地站在一个地方。
她俩多多号人,排成一列,走在最前方的人背着音箱,用麦克(麦克)风带领着后边的人流,前面的人随后他,沿着河边一边称心快意一边走着,不知情的人还觉得这是怎样邪教社团在练功呢。
这时候姜来还给他们取了个特别诗意的名字——蛇舞。

夜间的古都特别安静,小城市的夜晚为主如此,没有过多的喧哗,也不会过度繁华,一切所得其所。
回来青旅,楼下的步行街商铺也都关门了。
姜来在床上,把我们旅游中国的故事分享给同屋的伙伴们,姜来讲到兴奋的时候两眼会发光,当然,也必不可少加盐加醋,讲到前边的时候,这曾经是一个天马行空的故事,我赶忙打断了他,让他急匆匆睡觉。
第二整日还没亮,我叫醒了还在梦幻中的姜来,同屋的人还在梦乡,有的在呻吟,还有的在说着梦话。
我们到楼下快餐店吃过早餐,穿过西门,上了公交车,准备前往云冈石窟,我来邵阳的显要目标,就是为了它。
今每一天色阴沉,大地和天空融为一体,都是惨淡的。
咱俩坐了一个多时辰的车,终于到达了云冈石窟。

云冈石窟不大,为了让青山绿水更有可看性,在石窟外面还挖了一个人工湖,人工湖上是一座新盖的庙宇,这个人工搭建的新景色,当年曾被国家文物局叫停,据说是放心不下湖水蒸发,会对石窟岩体造成不可挽回的毁损。至于为啥还是可以建成,也许是政治和经济之间达成的某种妥协吧。
本来,若干年过后,那么些新造的青山绿水,也许又会重复变成新文物,历史嘛,本来就是一个幽默的循环。

走进洞窟,我到底见到了原始人对佛法的敞亮,他们为无形的佛赐予各色各种有形的千姿百态,用雕刻在石头上的故事,循循导人向善。每一尊佛像,每一个花纹,每一幅摄影,甚至每一个笑容,都藏着麻烦言喻的神性,空气里面,除了漂浮着的煤尘,还有佛的毅力。
洞窟依山而凿,从西晋至今,已经有一千多年的历史,时光凝固在石头上,记录了千年的风霜。
第二十窟的大佛在云冈石窟的最主旨,裸露在外的释迦坐像面象祥和,目光慈悲,俯视着云云众生,不过,第二十窟的大佛并不是最大的。
最大的佛像,其实藏在被半座山覆盖的第三窟里,不过,唯独光明普照的第二十窟获取了最多信徒,他们在大佛面前烧香跪拜,认真诚恳。
姜来站在大佛面前,双手合十一拜。
“这一次许了什么愿吗?”我问。
“没有许。在华严寺许过了。”他说。“我即便佛祖,我也会烦。”
我笑了。

忽然,我见状站在姜来旁边的,是我们今日五回相遇的小妞,她双手合十,头有点低着,闭着双眼,无声地念道,好像在念经。
自身用眼神向姜来表示,姜来回眸了一下,默默地说了句:“都第两回碰着这女的了,她怎么阴魂不散。好吓人。”
姜来认为自己一度低于了音响,其实并不曾,他的话被女人听到了。
她睁开双眼,走向前,小心翼翼地对姜来说:“你……是姜来吗?”
姜来被吓了一跳,快捷问:“我是,你是……?”
“你果然是个大傻逼,姜来,我是年莲,你不记得我了吧?”

逛完云冈石窟,大家共同重回了市区。我们是指——我,姜来,年莲。
在回到的车上,姜来和自身晾在一边,和年莲有说有笑。
原先,年莲是姜来的小学同学,他们自从小学毕业将来,就再也没见了,十多年之后的前几日,他们在漫漫的黑龙江聊城,以万分偶然的格局重新相遇,当姜来听到年莲这多少个名字的时候,他触动地和他相认,这种久别重逢,看上去真值得恭喜。
“谢已,我报告你,年莲是自个儿小学的好对象,从小学五年级起首,她就从头追自己了。可自己对她一些兴趣都尚未,一贯不肯他,不过,她那孜孜不倦的旺盛……”姜来刹车了弹指间,卖了个典型。
“…….最后依旧没能打动自己,小学毕业之后,我们就再也一向不会见了。”姜来毫不掩饰着团结过去的自豪,当着年莲的面说。
“你这人真不会说话,哪有您如此的人介绍对象的。”我说。
“没关系,没提到,我自小学二年级就认识姜来,他这德行,我最明亮了。”年莲长发飘飘,长得很甜美,说话的动静特别晴朗。
“所以自己说得没错吗。你啊,从小起头就不放过我,果然就是阴魂不散。哈哈哈。”姜来说。
“我从九龙壁出来,看到了你,这时候我还不肯定你是不是姜来,我认为自己眼花了,毕竟十多年没见了,我难以置信自己看错了,于是,我转身就走。”
“第二次在城墙下边骑自行车,我远远地就看看您了,你放在心上着骑自行车,没见到本人,但自我盯着您,看了很久很久,我心里面在想,难道她着实是姜来?但天太暗,我要么不确定。”
“直到明日白天,第五遍遭遇你,我才认准了你就是姜来,隔了这么久,你要么那么的帅,看来当年的自身,眼光依旧很准的。可您的人性一点也没变,大白天乱说人家坏话的性情如故老样子。”年莲笑着说。
“这一点我同意,他的嘴巴好像是得了帕金森综合症,没法治。”我笑着说。
“嘿嘿嘿,嘿嘿嘿。”姜来窘迫地笑起来。
“年莲,我跟你赔礼道歉,你领会我直接是口直心快,心善嘴贱,怪我没意见,没认出你,何人让您现在长得这般美,换做是其外人,我相信也认不出来。”姜来说。
这话听上去像是道歉,其实是变着法子在赞年莲,年莲听完事后,欢欣鼓舞。
“嘴甜舌滑的家伙。如若你十多年前答应自己的求偶,你现在就足以占据我的美了。可惜现在,你现在没机会了。”年莲说话的时候,不上心起撩起自己的毛发,阵阵抚媚从头发表露。
“没悟出,你长成了未来也和自家一样不要脸。对了,你怎么也来安顺了?”姜来问。
“这题目,我也要问您呀。你怎么也来泰安了。”年莲反过来问。
“我们在出游中国啊。我们都已经出来走了快一个月了。”姜来骄傲地对着年莲说,声音明亮满面春风,车上的人都听见了。
“这么狠心。我是来玉溪散散心,我正准备离婚吧。”年莲若无其事地协商。
“离婚!离婚?”姜来再一次了一次以确认自己并未听错。
“对,你没听错,我要离婚。像本人这样妩媚动人的巾帼,当然早就结婚了,高校毕业没多长时间,我就结婚了,这都或多或少年前的事了。你呢?老婆孩子还可以吗?”年莲问。
“什么老婆孩子,我还没成家吧,老子才26岁好还是不好,”姜来骄傲地说。
“你们男人就是好,越老越热门,不像大家这些女生,假若过了25岁还没嫁出去,这就是天大的劫数了。”年莲说。
“可您如此早嫁出去,现在不也是要离婚么?对了,你干吗要离婚呢?”姜来问。
“说起来,很复杂。到站了,大家下车吧。”年莲主动牵着姜来手,下了车,他们的涉嫌,难道已经提高了?

回来市区已经是早上,年莲带我们到一家很出色的河北餐馆吃晚饭,她点了满桌的粉条,饿了一天的大家,吃东西的时候就像四只饕餮,海南果然是中国面食之都,只有你意外,没有做不出去的,当自身看齐灿烂的粉条在自己后面,我突然想起了一句广告词:
世界面食在炎黄,中国面食在四川。
姜来和年莲聊得动感,点了一瓶河北最有名的西凤酒,我愕然地尝了一口,辣得我浑身发烫,鸡尾酒实际是太吓人了。
他们俩喝得淋漓神采飞扬,年莲的脸已经通红,据说喝酒脸红的人实在最不合乎喝酒,得咽急性鼻咽炎,食道癌,胃癌的几率是常人的几十倍。
本人也是个一喝酒就会脸红的人,所以自己不爱喝酒。有人由此表彰自己专门会养生,其实,这不过是本人对人身自私的另外一种表现情势。

“姜来,你现在有女对象吗?”借着酒意,年莲尝试更深切地询问姜来,当一个妇人问此外一个男人有没有女对象,这纯属是司马昭之心。
“没有,我觉得一个人挺好的。”姜来随口就回应,看来她不懂女生的思想。
继之,年莲问了一个,这怕自己这些别人听了也极其窘迫的题目,她说:
“倘若自己离婚了,你愿意和当今的自我在协同呢?”她刻意把“现在的本人”这个字说得专程重。
姜来先是愣了愣,然后哈哈哈大笑起来,餐厅里的人都听到了。
“废话,当然是不甘于了。这应当是我首先千零一遍的不容啊。”
“姜来,你要么那么讨人厌,一点都不会迁就别人。”年莲摆出了一副臭脸,可这幅臭脸,也是可爱的臭脸。
“下次能换个笑话啊?从五年级到前几日,你还不放过我呀。”姜来借着酒气,坐在年莲身边,右手搭在他的肩膀上,就像五个好哥们。
“臭流氓,放手你的咸猪手,什么叫不放过你,我但是有夫之妇。”
“可您神速就足以解脱了。”
“哎,别提那破事了。来来来,喝酒喝酒。”年莲拿起酒杯,发现其间已经没酒,我赶忙给她酒杯倒满酒。
“对了,姜来,你也没跟自家美观介绍你朋友。你们是怎么认识的呀。”年莲对着姜来问道,姜来把前晚讲给青旅小伙伴的故事复述了五回,姜来每一回讲故事,都用尽各样夸张的修辞手法。
就像其别人一样,年莲也听得入神。
“谢已,姜来,你们三个的经历丰硕写一本书了。我真佩服你们。”年莲说。“可是,我总觉得,你们的故事,好像差了点什么。”
“什么事物?”我和姜来异口同声地问。
“差一个女主角!”年莲说。
我和姜来面面相觑,姜来和自己的想法应该是均等的,那是我们目前才建立的默契。
“难道,年莲,你是,想当以此故事里面的,女主角?你该不会,想和我们联合游览中国啊?”我很认真地问他,我期望不用被自己说中,千万万万。
“什么哟,我才不情愿和你们住青旅睡火车每一日累得像只狗似的。我是想说,你们要不要听我的离婚故事,你们假使写环游中国的回想录,我的故事,应该能在里边,占一坐席吗。”
自己和姜来松了一口气,原来他只想把故事分享给我们。
中华很大,环游中国的征途很窄,六个人同行,已经是终端了,几人同行?我的天啊,我确实不敢想象。

年莲喝了一整瓶四特酒觉得还不够过瘾,提出我们换个旅社喝酒。
我们看了下时间,已经快十一点了,昨日清早还要赶火车,我们拒绝了她的邀请。
“年莲,前几天和您久别重逢,我特别称心快意,不过,大家明日清早快要出发,我真的无法再喝了,明日自己如果起不来,这就崩溃了,谢已会把我杀了。”姜来说。
年莲跟着我们走出餐厅的大门,走路摇摇晃晃地,就像一只醉醺醺的企鹅,她在餐厅门口拉着姜来不放,姜来一脸无奈地看着自身,他在向本人求救,我爱莫能助。
说时迟这时快,年莲坐在地上,哭了四起,哭得撕心裂肺的。
自己和姜来不久把她扶他到酒馆旁,姜来说我们错了,我们去酒吧陪你喝,你绝不哭,你绝不哭。
我们像哄孩子无异安慰着她,然则他一句话也不说,除了安慰她,叫她不要哭之外,大家都不明白该说怎么着好,刚才还不错地,怎么突然就哭起来,我和姜来都无语了。

借着路灯,我看齐长发之下的她,妆容已经哭花,泪水在眼角下开发了一条流向悲伤的河床,河床下是血和肉,她的样板,令人特地同情。
她缓了缓,抬先导,用已经哭肿的当即着我们,她哽咽说着令人无限惋惜的话:
“你们能陪我回来办离婚手续吗?我一个人,实在撑不下去了。我求你们了。姜来,谢已,我求求你们。我…真的老大了……”
话还没说完,年莲就昏迷在地上,准确地说,应该是醉倒了,她一身酒气,即使远隔三米之外,都能闻得到。
路边走过的人,看了俺们一眼,快速地偏离,事不关己,高高挂起。
“现在如何是好?”我问姜来。
姜来把年莲的头放在自己的大腿上,他轻轻地摇了摇她的头,除了散落在地上的头发在袅袅,仍旧不曾反应,她的熨帖的呼吸声是绝无仅有申明他还活着的痕迹。
“那下子,我也不了然该肿么办了。”姜来很不得已地说。
年莲的手提袋被甩到路边,我走过去,捡了回到,手提袋的疙瘩没有扣好,往上一提,里面东西就全都滚出来,眉笔,粉饼,钱包,驾照,各样杂七乱八的事物混杂在一道成了个杂货铺,当自身还在思想着,为何女性的包包会这么复杂的时候,我看到伴随着一堆化妆品一并掉落在地上的,还有一把手掌那么长的刀,哐当一声,清脆响亮。
口红,相对是一个女孩子最有杀伤力的军火,这把武器,几乎各样女子的包里都持有。
然则,年莲比任何的女郎更复杂,我平素没见过哪些女子会在包里放一把刀。
姜来也看出从包里掉出来的刀,吓了一跳。
年莲的左边垂在地上,姜来把他的手抬起来,借着路灯暖黄的光,仔细端详,看完事后,他一脸惶恐。
本人尽快把地上的事物一股脑丢到包里,走到年莲的身边,姜来抬起他的手腕,我看出姜来脸上的惊恐的出处。
年莲的手腕上,好几道刀割的伤痕已经结了痂,鲜红的伤痕相互纠缠,如掌纹一般根植在手腕上,假使没有错的话,这应该就是传说中的自杀未遂。
设若伤感是种传染病,被感染的,肯定是左手。我举起年莲的左手,果然,左手下边也如出一辙布满疤痕,五只手腕,已经被百般摧残,看不出原来的样子。
现在,这种无名的传染病也跑到自家身上,自从我触碰着年莲的手,身上的每一个毛孔,就起首不停地打哆嗦。
自我豁然想起,刚收拾东西的时候,有一张快捷旅舍的房卡在本人眼前一闪而过。我尽快把包里的事物尽数倒出来在地上翻找,这把沾满血腥的刀也夹杂在其间,姜来把刀从杂物里挑出来,翻过来翻过去看了几眼,然后,他把刀扔到一侧的垃圾桶里,刀在空中转了几圈,经过了一条圆润的抛物线,准确地落在了垃圾箱里。
自身拨开一堆化妆品,找到了房卡,房卡上边写着旅舍的地址,酒店的岗位,就在我们青旅的背面。
自己和姜来在路边拦了一辆出租车,把年莲抬上了车之后,我让司机带大家到年莲住的商旅。
运城古城不大,出租车开不到5秒钟就到酒楼了。
酒吧没有电梯,姜来只可以背着年莲,困苦地往上爬,商旅的房间号很奇怪,年莲的屋子,位于20/3/1号,这不是指2楼31号房,而是指第20家子公司/3楼/1号房,那代表,我们要爬上3楼。
还好年莲不重,姜来仍可以一举把他背上3楼,房间的门口挂着一块请勿打扰的牌子,应该是年莲出门的时候挂上去的,我把它取下,打开房门,重新把它挂上。
姜来把年莲丢到床上,喘了两口大气,我见到她全身都在冒着热气,像一个刚出炉的包子。

打开灯,我看到的,是一个双人房,我不太清楚为何她一个人却要定双人房,在本人眼里,她并不是一个妇人,而是无数个问题的集合体。
屋子里的行头到处都是,奶罩和底裤随随便便地丢在地上,马桶上,卫生间的地点,还有一块刚用过的废纸,下面沾满了鲜血,已经发黑,洗手盘下边的护肤品,化妆品,堆成一座小土丘。
早已凌晨12点了,时间也不早了。
本人问姜来:“我们要不回青旅吧。”
姜来没说话,看着年莲,陷入了沉思。
“要不,我们明日夜晚就在这边睡呢,反正这里有两张床,大家可以照看一下他。”我说。
姜来只说了一个好字。
姜来帮年莲脱掉鞋子,盖好了被子,还细心地,将他头上的头发理了理,她看上去,就像童话里的睡美人,不过,这样的佳丽,为啥要自残,我确实想不通。
姜来说他不困,让自己先睡,他把房间的灯关上从此,坐在沙发上,打开电视机,调成静音,电视机里播着地方电视机台的电视机节目,中间还插播着中午电视机购物广告,广告里的召集人嘴巴张得宏大,表情相当增长,他们正声嘶力竭的叫卖着保健品,收藏品,奢侈品……
静音之后,他们都成了哑巴,也许只有聋子才听到他们的话。
姜来在房间里点了一根烟,一呼一吸之间,火光或明或暗,就像一只萤火虫,电视机屏幕发出的蓝光打在烟雾上,烟雾把姜来的脸笼罩,逐渐地,我看不清姜来了。
躺在床上,眼睛盯着电视机的屏幕,逐步失去了问题,我是一台老相机,快门和光圈都松弛了,我记得自己双眼拍下的终极一幅画面,是姜来默默抽烟的榜样。

中午茶时间,冲泡柠檬红茶,参与少许蜂蜜,用勺子搅拌。

1> NS_OPTIONS与位运算

NS_OPTIONS用来定义位移相关操作的枚举值,当一个枚举变量需要指引多种值的时候就需要,我们得以参考UI基特.Framework的头文件,可以看出大量的枚举定义。例如在SDWebImage下边就会触发到SDWebImageOptions枚举值:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,
    SDWebImageLowPriority = 1 << 1,
    SDWebImageCacheMemoryOnly = 1 << 2,
    SDWebImageProgressiveDownload = 1 << 3,
    SDWebImageRefreshCached = 1 << 4,
    SDWebImageContinueInBackground = 1 << 5,
    SDWebImageHandleCookies = 1 << 6,
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};

“<<”是位运算中的左移运算符,第一个值SDWebImageRetryFailed = 1
<<
0,十进制1转化为二进制:0b00000001,这里<<0将所有二进制位左移0位,那么仍然0b00000001,最后SDWebImageRetryFailed
值为1.
第二个枚举值SDWebImageLowPriority
=1<<1,这里是将1的二进制所有位向左移动1位,空缺的用0补齐,那么0b00000001变成0b00000010,十进制为2则SDWebImageLowPriority值为2。

左移1位示意图

依次类推:
SDWebImageCacheMemoryOnly向左移动2位等于4,
SDWebImageProgressiveDownload向左移动3位等于8.
上面写一个,customImageView是大家自定义的imageView实例,在SDWebImage的SDWebImageManager.m具体应用中:

   [customImageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];

留意到代码中用到了”|”,‘|’是位运算中的或运算,需要五个操作数,功效是将多少个数的一律位展开逻辑或运算,即只要三个对应位有一个位1,则运算后此位为1,即便两个对应位都为0。例如十进制1的二进制0b00000001
| 十进制2的二进制0b00000010,结果为0b00000011十进制为3。下图示例:

或运算

当options值为SDWebImageRetryFailed |
SDWebImageCacheMemoryOnly时,执行或运算0b00000001| 0b00000100 =
0b00000101 十进制是5.
那么在切切实实的措施内部options怎么拔取啊?下边的代码SD将options和SDWebImageRetryFailed做”&”运算:

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    }

‘&’是位运算中的与运算,当对应位数同为1结实才为1.诸如十进制1的二进制0b00000001&十进制2的二进制0b00000010,结果为0b00000000十进制是0.

与运算

下边代码中,SD将此时的options二进制0b00000101和SDWebImageRetryFailed的二进制举行&运算,假若options包含了SDWebImageRetryFailed则结果为真。SD通篇都遵照options做了过多作业,由此领悟可选枚举和位运算分外首要。

第十三章(-):鱼城

围绕在自己身边的,是四条金鱼,金鱼的颜色对应着风,火,水,土四种因素。
代表风的金鱼是一朵云,隐约看到轮廓,在它身边有稀有风暴包裹着;
代表水的金鱼是一团火,像燃烧着的火种,火苗在它身上四处乱窜;
意味着水的金鱼是一汪海,水花源源不断地频频从它身上溅出;
代表土的金鱼是一块泥,干枯的表面上布满了裂痕,碎泥成了灰纷纷扬扬。

他俩在自己的身边,就像四大保障,形影不离,当我想触碰他们的时候,他们会快速游走,手收回来,他们又自行游回来。
“你好幸运。”一个老头子走到自己的身边对自身说。
他满头白发,脸上的深壑的皱纹都得以夹死苍蝇,肉色的长辈斑比太阳黑子还要黑,他拄着拐杖,走路很缓慢,每走一步,都像过去了一个世纪。
“为啥我是万幸的?”我反问她。
“你抱有了这个宇宙,还不够幸运吗?”
本身往四周看了看,我真站在一片荒芜之地,寸草不生,假若说我具备了这么些宇宙,这自己这个宇宙也未免太可怜了。
老头子用拐杖戳了戳我身边的金鱼,他们急速地逃脱,游到我的身后,就像五个小孩,看到陌生人,会害怕。
“死亡是这一个宇宙里最自私的赠礼。它没有慷慨,但也未尝敬服。你刚到手了它,随之而来的,就是新的宇宙。”
“你是说,我死了?”
“不不不,在无边的宇宙空间里,一向没有人能真正地死去。死亡然而是个经过,不是终点。每三遍死亡,随之而来的,都是全新的新兴,而你,拥有的,就是一个属于你的新的自然界。”
“这是自我的天体?”我很怀疑自己要好有没有听错,这种话,进了什么人的耳根,都会以为不可信。
“是的,你的,全体,都是您的。”老头子背对着我,把拐杖插进地里,抬先导,举起粗糙的双手,他的动作,有一种宗教仪式感。
“这你干什么会并发在自我的大自然里。”我对着他的背影说。
他把手放下,回过头,对着我说:
“你这多少个题材,问错了。再问两遍。”
自己有点疑惑,但自我要么照做了。
“为啥自己的宇宙里,只有我和您?”
“又错了。再来。”
身边的鱼群继续在半空中游着,他们开展的楷模让我想起了时辰候的和谐,假若一切的想想都来自生死,那么,唯一的问题,也只是关于生死本身。
“为啥,你还并未死。”我问了一个听上去类似不太礼貌的题目,可自我只可以想到这多少个问题。
“你终于问到点子上了。”
他拄着拐杖走到本人的身边,离自己只有一个拳头的距离,我们过于接近,空气中,我甚至能闻到她嘴里陈年的含意,是一亿本历史书焚化过后的睿智的意味。
“我就是您创建的人,你想自己死,我便死,你想我活,我就活。”他谈话的话音带着平静,我能听出他言语里,这种主仆的含意,是温柔的服服帖帖,一心一意的尾随。
“倘使那是自个儿的自然界,为啥这边怎么都尚未,只有你?”我问。
“因为你的心尖,现在如何都未曾。”
“这你究竟是什么人呢?”
“我是您的先生,我是你的主,我是您的神,我也是您的公仆,你创立自己的时候,就是如此定义自己的。”
“我是什么样时候,成立你的?”
“在你需要自我的时候,你就成立了我。”
“我怎么样都得以创建?”
“对,你的金鱼会为你创制一切,他们就是以此世界的为主造型,有了他们,你如何都可以创制。”
这话听起来很令人兴奋,成立一切?这我不就是神了?
“我想要一条河,一条流向天空的河。”我说。
四条金鱼动起来,一眨眼的功夫,一条巨大的江湖就应运而生在我的身边,河流无视着地力和宇宙规则,向天奔流。
老伴儿笑了笑,什么都不说。
“我想要一座结冰的火山。”
老伴儿笑了笑,什么都不说。
“我想要长满鱼鳞的鸟。”
老伴儿笑了笑,什么都不说。
“我想要一座黄金造的城堡。”
老伴儿笑了笑,什么都不说。
“我想要…….”

自身花了很长日子,从零到有,把一个整机的世界创设出来,这里有百分之百我想要实现的奇异事物,身边的荒废早就没有,取而代之的是一个一心陌生的社会风气,一个只适合存在幻想中的世界。
本身看着他们,却怎么都乐滋滋不起来。
其一世界,无论从什么角度看,都特另外违和。
长着腿的蛇在街头巷尾爬行,三个乳房的女士和有三根阴茎的先生在赤裸裸地做爱,空中的UFO不断掉落奇形怪状的外星人,天空中悬浮着一座座山,山底还有成片的荒漠,会喷火的狗还有会友善烹饪的猫,这不是一个正规的社会风气,然而,这就是本身想要的世界?
当年的好奇心已经消失,只剩余和这一个世界无穷无尽的懊悔,当自家想问老伴,有没有法子重新再来的时候,他早已熄灭得无影无踪。
“我想要老头子重新出现。”
老伴儿仍旧不曾出现。四条金鱼也一并消失了。这些世界,好像早就定型了,我再也无力回天改观。
本人站在这多少个世界的最上端,以上帝的意见看着自身成立的凡事,我的子民,我的国度,我的星斗,我的天体。
那就是自个儿的世界呢?这实在是自身的社会风气吧?

我忽然想起老头子的话。
可能我死了,这一体就可以重头再来,我想要死亡。
本人纵身跳下,一心寻死,我知道,自由落体运动最终会给本人带来一个崭新的自然界。
自家坚信不移。

目录 《相对光年》小说

未完待续,欢迎关注!(14/31)

有关作者:
aloho
。定位为一个幽伤主义的散文家。男,在大香港生活,正朝着三字头奔赴,搞过摄影和电音,做过网金产品主任,偶尔是一个只做LOGO的平面设计师,拥有一个最为连载的私有诗计划[aloho的床头诗]

2016年出境游中国,回来后憋了一年写本半记实半虚构的随笔《绝对光年》,自诩那是一本魔幻主义公路小说。

喝两口茶,感谢盛放着它们的器皿。不论是玻璃或是古瓷,依然此外的质量,都有分别适合的盛放液体。并不是双边的秉性相投,而是那样的映衬给予了饮用者更好的享用质感。唇齿与杯子触碰,液体则深化感官。回旋下咽的鱼水之欢。

2> NSURLCredential

当移动端和服务器在传输过程中,服务端有可能在再次回到Response时顺手表明,询问
HTTP
请求的发起方是何人,这时候发起方应提供正确的用户名和密码(即认证音信)。这时候就需要NSURLCredential身份认证,更加切实可以查看这篇博客

茶味人生

3>涉及的宏定义

一年四季的转移到达此时大约,是步伐匆匆的。恍如一夜的赫然回暖,以及久违的暖阳天光,令人在跨出室内,被清风回旋着搂入怀中时,忍不住责怪道:这天气真是好的一塌糊涂。

3-1>FOUNDATION_EXPORT:

用来定义常量,和#define功能一样,只不过在检测字符串的值是否等于是比#define的频率更高,因为相比的是指针地址。

好友木十乔说,天气好的令人想结合。在我看来,婚姻都是剩下,去爱就好。

3-2>NS_DESIGNATED_INITIALIZER :

NS_DESIGNATED_INITIALIZER宏来实现指定构造器,日常是想告知调用者要用这些措施去发轫化类对象,便于规范API。

假诺在这么的时刻,碰着一个不同平时的人,优雅从容,活力丰硕,或许不论是何人,内心的湖面都会温澜潮生。

3-2>__deprecated_msg

用来指示此办法或性能已经丢掉。

@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");

欣逢的随时是两全其美的。它们的过来不需要征兆,一如此时平静的微风午后。

4>initialize

initialize静态方法会在率先次利用该类在此以前由运行期系统调用,而且仅调用一回,属于懒加载范畴,假若不拔取则不会调用,可以在措施内部做一些最先化操作,不过load方法是假若开动程序就会调用。关于initialize和load更加详细的看这里

一段恋情的开局,有时只是弹指间的大浪。你清晰地通晓,那一个站在您湖面上的人,他日夜兼程,跋涉前来。当她临在你的湖上,或许他并不开口,只是一个神采,一个行径,就让你心生迷恋。从他脚边席卷起的,将是无尽的温澜。

5>dispatch_barrier_sync

GCD中的知识点,承上启下,当把任务A添加到队列中应用dispatch_barrier_sync时,它会等待在它前边插入队列的职责先进行完,然后等任务执行完再履行后边的任务。更加详细的可点这里

当前整治了那一个,倘若还有其余必要讲的话,下文会再做表明。我们有不熟练的知识点也足以在评头论足中恢复生机,我会挑痛点相比多的当即更新到博客中。

您的湖水会因为她而温暖。

二.大旨源码解析

临在你的湖上

SDWebImage的核心Workflow:

SDWebImage-Workflow

咱俩要探讨的源码主假使围绕这么些骨干类进行。
开卷指出TIPS:SDWebImage的源码多,逻辑复杂,我的提出是读者下载一份源码,源码和本文同步阅读,因为假使没看过源码的调用逻辑,单看本文的解读不会形成类别。会在源码上加注释,并且copy到小说中,运行品种参考调用栈配合本文效果会更好。

也因为自身的心得而渐渐发觉,爱的给予者和接受者,是在一个能力层面的,才能树立连接。恋爱,并行不悖才好玩。任何一方压倒性的力量,都会损毁爱。

1>UIImageView+WebCache/UIView+WebCache

UIImageView+WebCache对外使用的API入口,那多少个类的接口设计把设计形式五大标准之一的接口分离原则反映的淋漓。首先说一下咋样是接口分离原则:
接口分离原则:为特定效能提供一定的接口,不要使用单一的总接口包括拥有功能,而是应该依照效益把那多少个接口分割,收缩依赖,不可以强迫用户去倚重那么些他们不使用的接口。
在.h中可以见见:

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options

这样调用者需要怎么样效用就去调用特定的API,清晰易扩充,在.m中会设计一个总的接口包含所有效率。UIImageView+WebCache紧假设一个接口,没有太多需要研商的,在.m中总接口中又调用了UIView的扩张方法,接下去讲一下UIView+WebCache。
UIView+WebCache提供了切实可行的图形加载请求,UIButton和UIImageView都可调用sd_internalSetImageWithURL来贯彻,下边具体看下sd_internalSetImageWithURL的实现。具体的源码意思都做了诠释:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context {
    //根据参数operationKey取消当前类所对应的下载Operation对象,如果operationKey为nil key取NSStringFromClass([self class])
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //具体的取消操作在UIView+WebCacheOperation中实现
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

    //利用关联对象给当前self实例绑定url key=imageURLKey value=url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //这里就用到了我们开篇讲的位运算,利用&与运算判断调用者是否需要设置占位图,需要则set
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

    if (url) {
        // check if activityView is enabled or not
        // 判断之前是否利用关联对象给self设置了显示菊花加载,如果有则add
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        //调用SDWebImageManager的loadImageWithURL方法去加载图片,返回值是SDWebImageCombinedOperation
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            //在这里移除菊花
            [sself sd_removeActivityIndicator];
            if (!sself) { return; }
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否不显示图片两个条件满足其一即可 1>调用者手动主动配置,哪怕image不为nil 2>没有图片并且不delaye占位图情况
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];
                }
                //如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, error, cacheType, url);
                }
            };

            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {//如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }

            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {//如果没有image,并且调用者设置了delaye显示默认图那这里targetImage设置为placeholder
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            BOOL shouldUseGlobalQueue = NO;
            //外部参数context如果设置了全局队列中setImage,那shouldUseGlobalQueue为YES,否则默认在dispatch_get_main_queue
            if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
                shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
            }
            dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();

            dispatch_queue_async_safe(targetQueue, ^{//队列中设置image给imageView或者button
                [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                dispatch_main_async_safe(callCompletedBlockClojure);
            });
        }];
        //绑定operation到当前self,key=validOperationKey,value=operation
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {v
        dispatch_main_async_safe(^{
            //移除菊花 抛出url为nil的回调
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

下边是在这一历程的概括流程图援助精通:

你的湖承载不了他的分量,或是他,平复不了你的险峻,都是令人无奈的。

2>SDWebImageManager

SDWebImageManager类是SDWebImage中的主旨类,紧要担负调用SDWebImageDownloader举办图片下载,以及在下载之后选择SDImageCache举办图纸缓存。并且此类仍是可以够跳过UIImageViewe/Cache或者UIView/Cache单独使用,不仅局限于一个UIView。
SDWebImageManager.h注解:

@class SDWebImageManager;

@protocol SDWebImageManagerDelegate <NSObject>

@optional

//当缓存没有发现当前图片,那么会查看调用者是否实现改方法,如果return一个no,则不会继续下载这张图片
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;

//当图片下载完成但是未添加到缓存里面,这时候调用该方法可以给图片旋转方向,注意是异步执行, 防止组织主线程
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

@end

@interface SDWebImageManager : NSObject
//SDWebImageManagerDelegate的delegate
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
//缓存中心
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//下载中心
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
//这个缓存block的作用是,在block内部进行缓存key的生成并return,key就是根据图片url根据规则生成,sd的缓存策略就是key是图片url,value就是image
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
//返回SDWebImageManager的单例
+ (nonnull instancetype)sharedManager;
//根据特定的cache和downloader生成一个新的SDWebImageManager
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
//下载图片的关键方法,第一个参数图片url,第二个参数设置下载多样操作,第三个参数下载中进度block,第四个参数下载完成后回调
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;
//缓存图片根据指定的url和image
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
//取消所有当前的operation
- (void)cancelAll;
//检查是否有图片正在下载
- (BOOL)isRunning;
//异步检查图片是否已经缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//检查图片是否缓存 在磁盘中
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//给定一个url返回缓存的字符串key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

@end

SDWebImageManager.m注解:

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
//是否取消当前所有操作
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
//没有参数取消回调
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
//执行缓存的操作
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

@interface SDWebImageManager ()
//缓存对象
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
//下载对象
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
//集合存储所有下载失败的图片url
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//存储正在执行下载图片操作的数组
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;

@end

@implementation SDWebImageManager
//生成一个SDWebImagemanager的单例
+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
//初始化SDImageCache/SDWebImageDownloade
- (nonnull instancetype)init {
    SDImageCache *cache = [SDImageCache sharedImageCache];
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    return [self initWithCache:cache downloader:downloader];
}
//初始化以及属性绑定
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}
//根据URL获取缓存中的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
    if (!url) {
        return @"";
    }

    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return url.absoluteString;
    }
}
//检查缓存中是否缓存了当前url对应的图片-先判断内存缓存、再判断磁盘缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    //判断内存缓存是否存在
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);

    if (isInMemoryCache) {
        // making sure we call the completion block on the main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    //判断磁盘缓存中是否存在
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//根据URL判断磁盘缓存中是否存在图片
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];

    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//进行图片下载操作
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    //completedBlock为nil,则触发断言,程序crash
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    //封装下载操作的对象
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        //为了防止在多线程访问出现问题,创建互斥锁
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //如果url为nil,或者没有设置失败url重新下载的配置且该url已经下载失败过,那么返回失败的回调
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    //创建互斥锁,添加operation到数组中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
    //使用缓存对象,根据key去寻找查找
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        //如果当前操作被取消,则remove且return
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        //(如果没有图片缓存或者设置了重新刷新缓存)且调用代理允许下载图片
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //如果有(其实是缓存的)并且调用者设置了重新刷新缓存,那么先把图片结果回调出去,然后继续去下载图片再更新缓存
            if (cachedImage && options & SDWebImageRefreshCached) {
                // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

            // download if no image or requested to refresh anyway, and download allowed by delegate
            //如果缓存没有图片或者请求刷新,并且通过代理下载图片,那么则下载图片
            //下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;

            if (cachedImage && options & SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //在这里真正调用imageDownloader去下载图片了
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                //操作取消则不做任何处理
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        //下载失败则添加图片url到failedURLs集合
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //虽然下载失败,但是如果设置了可以重新下载失败的url则remove该url
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否需要缓存在磁盘
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    //图片下载成功并且判断是否需要转换图片
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //根据代理获取转换后的图片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            //如果转换图片存在且下载图片操作已完成 则在缓存对象中存储图片
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }

                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        //下载完成且有image则缓存图片
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                //如果下载和缓存都完成了则删除操作队列中的operation
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @synchronized(operation) {
                // Need same lock to ensure cancelBlock called because cancel method can be called in different queue
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
        } else if (cachedImage) {
            // 有图片且线程没有被取消,则返回有图片的completedBlock
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            // Image not in cache and download disallowed by delegate
            //没有在缓存中并且代理方法也不允许下载则回调失败
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}
//将图片存入缓存
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
    if (image && url) {
        NSString *key = [self cacheKeyForURL:url];
        [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
    }
}
//取消所有的下载操作
- (void)cancelAll {
    @synchronized (self.runningOperations) {
        NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}
//判断当前是否有下载图片
- (BOOL)isRunning {
    BOOL isRunning = NO;
    @synchronized (self.runningOperations) {
        isRunning = (self.runningOperations.count > 0);
    }
    return isRunning;
}
//线程安全的移除下载operation
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    dispatch_main_async_safe(^{
        if (operation && !operation.isCancelled && completionBlock) {
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}

@end


@implementation SDWebImageCombinedOperation
//set方法
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}
//SDWebImageOperation协议方法
- (void)cancel {
    @synchronized(self) {
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.cancelBlock) {
            self.cancelBlock();
            self.cancelBlock = nil;
        }
    }
}

@end

下边是SDWebImageManager的重中之重节点流程图:

正如温暖人心的,不光可以是成百上千美好的情丝,也足以是彻头彻尾的留存。和煦阳光,悦耳的音乐,值得留恋的地点,质感独特的书本,设计有趣的物件,或是一句临别前的言语。

3>SDImageCache

SDImageCache是SDWebImage的缓存主题。分三局部组成memory内存缓存,disk硬盘缓存和无缓存组成。
SDImageCache.h文件讲明:

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    无缓存类型
    SDImageCacheTypeNone,
    磁盘缓存
    SDImageCacheTypeDisk,
    内存缓存
    SDImageCacheTypeMemory
};
@interface SDImageCache : NSObject
#pragma mark - Properties
//缓存配置对象,包含所有配置项
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//设置内存容量大小
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//设置内存缓存最大值limit
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

#pragma mark - Singleton and initialization
//返回SDImageCache单例
+ (nonnull instancetype)sharedImageCache;
//根据特定的namespace返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
//根据特定的namespace和directory返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

#pragma mark - Cache paths
//生成缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
//添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path;

#pragma mark - Store Ops
//根据key去异步缓存image,缓存在内存和磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘 多加一个imageData图片data
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,缓存在磁盘
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//在缓存中查询对应key的数据
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
//在磁盘缓存中查询对应key的图片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory;
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
//异步清除所有的失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize;
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount;
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;

#pragma mark - Cache Paths
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;

@end

SDImageCache.m注解:

// See https://github.com/rs/SDWebImage/pull/1141 for discussion
//自定义内存缓存类
@interface AutoPurgeCache : NSCache
@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {
    self = [super init];
    if (self) {
#if SD_UIKIT
        //添加通知,当受到内存警告则移除所有的缓存对象
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

//内联函数获得该图片的缓存大小 注意乘以屏幕的比例
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

@interface SDImageCache ()

#pragma mark - Properties
//内存缓存对象
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//保存缓存路径的数组
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
//执行处理输入输出的队列
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;

@end


@implementation SDImageCache {
    NSFileManager *_fileManager;
}

#pragma mark - Singleton, init, dealloc
//生成单例SDImageCache
+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (instancetype)init {
    return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}
//初始化磁盘缓存路径和内存缓存name
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

        // Create IO serial queue
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        _config = [[SDImageCacheConfig alloc] init];

        // Init the memory cache
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // Init the disk cache
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }

    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    SDDispatchQueueRelease(_ioQueue);
}

- (void)checkIfQueueIsIOQueue {
    //dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)获取当前队列的名字
    //dispatch_queue_get_label获取队列的名字,如果队列没有名字,返回NULL
    const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
    if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
        NSLog(@"This method should be called from the ioQueue");
    }
}

#pragma mark - Cache paths
////添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
    if (!self.customPaths) {
        self.customPaths = [NSMutableArray new];
    }

    if (![self.customPaths containsObject:path]) {
        [self.customPaths addObject:path];
    }
}
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:filename];
}
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
//根据key值生成文件名:采用MD5
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}
//生成磁盘路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

#pragma mark - Store Ops

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //缓存到内存
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //缓存到磁盘,采用异步操作
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, use PNG format
                    //如果没有data则采用png的格式进行format
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
                }
                [self storeImageDataToDisk:data forKey:key];
            }

            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}
//利用key进行缓存data
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }

    [self checkIfQueueIsIOQueue];
    //如果文件中不存在磁盘缓存路径 则创建
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }

    // get cache Path for image key  得到该key的缓存路径
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl  将缓存路径转化为url
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //将imageData存储起来
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

    // disable iCloud backup  如果调用者关闭icloud 关闭iCloud备份
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    dispatch_async(_ioQueue, ^{
        BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        if (!exists) {
            exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(exists);
            });
        }
    });
}
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memCache objectForKey:key];
}
//在磁盘缓存中查询对应key的图片 并且如果允许内存缓存则在内存中缓存
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }

    // Second check the disk cache...
    image = [self imageFromDiskCacheForKey:key];
    return image;
}
//根据key在磁盘缓存中搜索图片data
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    NSString *defaultPath = [self defaultCachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    NSArray<NSString *> *customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        NSString *filePath = [self cachePathForKey:key inPath:path];
        NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }
    }

    return nil;
}
//根据key在磁盘缓存中搜索图片dimage
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
        //根据图片的scale或图片中的图片组 重新计算返回一张新图片
        image = [self scaledImageForKey:key image:image];
        if (self.config.shouldDecompressImages) {
            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
        }
        return image;
    } else {
        return nil;
    }
}

- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}
//在缓存中查询对应key的图片信息 包含image,diskData以及缓存的类型
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...如果内存缓存包含图片数据则回调结束
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {//imageData都存储在磁盘
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //到这里说明已经来到磁盘缓存区获取 然后回调结束
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    if (key == nil) {
        return;
    }
    //如果内存也缓存了,则删除内存缓存
    if (self.config.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }

}

# pragma mark - Mem Cache settings

- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
    self.memCache.totalCostLimit = maxMemoryCost;
}

- (NSUInteger)maxMemoryCost {
    return self.memCache.totalCostLimit;
}

- (NSUInteger)maxMemoryCountLimit {
    return self.memCache.countLimit;
}

- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
    self.memCache.countLimit = maxCountLimit;
}

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
    dispatch_async(self.ioQueue, ^{
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

- (void)deleteOldFiles {
    [self deleteOldFilesWithCompletionBlock:nil];
}
//异步清除所有失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        //resourceKeys数组包含遍历文件的属性,NSURLIsDirectoryKey判断遍历到的URL所指对象是否是目录,
        //NSURLContentModificationDateKey判断遍历返回的URL所指项目的最后修改时间,NSURLTotalFileAllocatedSizeKey判断URL目录中所分配的空间大小
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // This enumerator prefetches useful properties for our cache files.
        //利用目录枚举器遍历指定磁盘缓存路径目录下的文件,从而我们活的文件大小,缓存时间等信息
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //计算过期时间,默认1周以前的缓存文件是过期失效
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        //保存遍历的文件url
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        //保存当前缓存的大小
        NSUInteger currentCacheSize = 0;

        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        //保存删除的文件url
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        //遍历目录枚举器,目的1删除过期文件 2纪录文件大小,以便于之后删除使用
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //获取指定url对应文件的指定三种属性的key和value
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            // Skip directories and errors.
            //如果是文件夹则返回
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // Remove files that are older than the expiration date;
            //获取指定url文件对应的修改日期
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            //如果修改日期大于指定日期,则加入要移除的数组里
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            // Store a reference to this file and account for its total size.
            //获取指定的url对应的文件的大小,并且把url与对应大小存入一个字典中
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        //删除所有最后修改日期大于指定日期的所有文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // If our remaining disk cache exceeds a configured maximum size, perform a second
        // size-based cleanup pass.  We delete the oldest files first.
        //如果当前缓存的大小超过了默认大小,则按照日期删除,直到缓存大小<默认大小的一半
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // Sort the remaining cache files by their last modification time (oldest first).
            //根据文件创建的时间排序
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // Delete files until we fall below our desired cache size.
            //迭代删除缓存,直到缓存大小是默认缓存大小的一半
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        //在主线程中回调
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

#if SD_UIKIT
//应用进入后台的时候,调用这个方法 然后清除过期图片
- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    [self deleteOldFilesWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}
#endif

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize {
    __block NSUInteger size = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount {
    __block NSUInteger count = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        count = fileEnumerator.allObjects.count;
    });
    return count;
}
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

    dispatch_async(self.ioQueue, ^{
        NSUInteger fileCount = 0;
        NSUInteger totalSize = 0;

        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:@[NSFileSize]
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        for (NSURL *fileURL in fileEnumerator) {
            NSNumber *fileSize;
            [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
            totalSize += fileSize.unsignedIntegerValue;
            fileCount += 1;
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(fileCount, totalSize);
            });
        }
    });
}

@end

下图是在SDWebImageManager中调用SDCacheImage查找缓存的严重性流程图:

此篇小说讲解的是UIImageView+WebCache/UIView+WebCache,SDWebImageManager和SDImageCache五个首要类,有成千上万很值得我们去学学的点,例如:
1.善用接口分离原则-设计更好的对外调用API。
2.适作为非常处理体制-这一个非常处理可以避免消耗不必要的资源依然很是发生。例如SDWebImageManager中

 if (!url) { return @"";}
 if ([url isKindOfClass:NSString.class]) {
     url = [NSURL URLWithString:(NSString *)url];
 }

3.增添互斥锁-起到线程的掩护效用。

@synchronized (self.failedURLs) {
     isFailedUrl = [self.failedURLs containsObject:url];
 }

4.多接纳inline函数-提升程序的执行效用。

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

5.巧妙利用封装思想和分支概念,写出更加平台化的零件-例如我们平日使用SD大多都是采取UIImageView+WebCache的API,
其实SDWebImageManager是一点一滴可以抽出来单独使用,不会因为跳过了UIImageView+WebCache没了依赖而一筹莫展利用。假使项目是索要三个人跨多部门通力合作,如果别人或其他机关索要调用你写的一个很牛逼的职能,这那么些平台化就很关键了。
SDWebImage还有众多值得我们借鉴和学习的地点,需要大家细细研读。下一篇会讲解SDWebImageDownloader和SDWebImageDownloaderOperation。

向左 o(* ̄3 ̄)o 向右

二〇一八年的五月末,决定去一个采暖的地点。于是坐上火车,前往乌镇。

黎明两点的列车,登上车厢,找到自己的床位。对面坐着一个喝着鸡尾酒的男人,他望见我,像是终于见到了一个足以说话的人,于是同自己交谈起来。他从南平(Hal)来,是一个庄园工人。他的期望是,要到香港去实现和谐的市值。他的营生让自家暴发了兴趣。我问她,园林工人要做些什么啊。

以此提问,让他因为十多少个时辰的日夜兼程和长日子处在狭小空间而凝结的憔悴面容一扫而光。他递给我一瓶清酒,兴奋地报告自己她所提交着汗珠的事业。

他告诉自己,大城市的园林规划,都很严刻。在绿茵和路面的交界处,草要和路面齐平,这就象征,草地的泥土要比路面低几公分。而她收拾的地面,一向都是很平整且齐平的。我看着他这样快乐地谈论着自己喜爱的做事,心生敬佩。

她忽然止住讲述,担忧地看着自己,用韵味十足的东北腔问我,迪拜是个好城市么?

这么些突如其来的题目,让自家一时不知什么回复。我看着他,沉默了一会,然后握过他的手,说,当然,是一个很好的都会,你可以在这里努力干活,你会过的很好。

Young

他听完自己的话,顿时表露爽朗的笑,勾过我的双肩,和自身碰杯。

一个三十六年没有踏出过家乡的东北男子,单纯朴实,怀揣着希望与企盼,前往彼岸未知的地,这才是当真地在生活。我笑着听他哼着歌谣,内心至极温暖如春。

新加坡站来到的太早,他也将惩治行装,准备踏上这片陌生的土地。临走前,他把自身叫醒,对本人说了句,小伙子,谢谢您让自己有了信心。我只是微笑,并在心中说,也谢谢你,让自身有了信念。

活着的意思,是体验存在加之的一切。

当您敞开自己,拔取这些不期而至的相逢,将来肯定有那么一天,你也能真心地对一个人说,因为际遇你,温澜潮生。

向左,向右

相关文章