OC版解決AsyncDisplayKit閃爍問題

解決Texture(原AsyncDisplayKit)的閃爍問題


AsyncDisplayKit 概覽

本文借鑒原文

Facebook 的Paper團隊給我們帶來另一個很棒的庫:AsyncDisplayKit法瑟。這個庫能讓你通過將圖像解碼癞志、布局以及渲染操作放在后臺線程宵喂,從而帶來超級響應的用戶界面,也就是說不再會因界面卡頓而阻斷用戶交互蜡娶。

初次使用, 當享受其一幀不掉如絲般柔滑的手感時,ASTableNode和ASCollectionNode刷新時的閃爍一定讓你幾度崩潰映穗,到AsyncDisplayKit的github上搜索閃爍相關(guān)issue窖张,會出來100多個問題。閃爍是AsyncDisplayKit與生俱來的問題蚁滋,聞名遐邇荤堪,而閃爍的體驗非常糟糕合陵。幸運的是,幾經(jīng)探索澄阳,AsyncDisplayKit的閃爍問題已經(jīng)完美解決拥知,這個完美指的是一幀不掉的同時沒有任何閃爍,同時也沒增加代碼的復雜度碎赢。

本篇文章將著重講解閃爍問題以及對應的解決方案低剔。

AsyncDisplayKit的閃爍總體上分為兩大類,

1)ASNetworkImageNode reload時的閃爍

當ASCellNode中包含ASNetworkImageNode時肮塞,reload這個cell, ASNetworkImageNode會異步從網(wǎng)絡請求或者本地緩存中獲取圖片襟齿,請求到圖片后再設置ASNetworkImageNode展示圖片,但在異步過程中枕赵,ASNetworkImageNode會展示PlaceHolderImage, 從PlaceHolderImage->fetched image的展示替換導致閃爍發(fā)生猜欺,即使整個cell的數(shù)據(jù)不變, reload時由于圖片的加載邏輯依然不變拷窜,仍然會閃爍开皿,對比我們常用的SDWebImage和YYWebImage, 它們的設置邏輯是先同步檢查是否有本地緩存,有直接顯示篮昧,沒有則展示placeholderImage赋荆, 等待加載完成再顯示加載圖片,展示邏輯即memory Cached image->placeholderImage->fetched image的邏輯懊昨,刷新的時候優(yōu)先級的不同窄潭,因此不會閃爍。

AsyncDisplayKit官方給的修復思路是:

? ASNetworkImageNode *imageNode = [ASNetworkImageNode new];
? imageNode.placeholderFadeDuration = 3;
? imageNode.placeholderColor = [UIColor redColor];

這樣修改后酵颁,確實沒有閃爍嫉你,但要的效果并不是我們想要的,這只是將閃爍問題用時間控制到3秒而已躏惋,并沒有實際解決問題均抽。

上面說到SDWebImage和YYWebImage的設置思路,可以給我們提供一定的思考其掂,如果我們繼承一個ASNetworkImageNode, 將ASNetworkImageNode的設置邏輯改為有cached image展示cache image油挥,沒有則重新從網(wǎng)絡請求,不是完美解決閃爍了嘛款熬?深寥!但事實并非如此,無論你怎么設置贤牛,同樣都會閃爍惋鹅。而我們知道在ASImageNode并不會出現(xiàn)這種問題,為什么不考慮適當?shù)臅r機進行替換呢殉簸,當我們有緩存的時候直接用ASImageNode替換ASNetworkImgeNode, 在這里可能有人會問闰集,這樣整個cellNode的控件已經(jīng)改變了9炼铩!武鲁!刷新怎么辦爽雄?這其實和ASTableNode的展示機制有關(guān)系,它并不是類似tableView的cell重用機制沐鼠,它所做的是每一個cellNode都是異步渲染加載的挚瘟,重新刷新意味著控件的重新排列(最直白的話,沒有用專業(yè)的術(shù)語)饲梭。言歸正傳乘盖,這里我們用最熟悉的YYImageCache橋接緩存問題,方便自由管理緩存問題,? 看解決方案:

```

@interface JSWebImageManager : YYWebImageManager<ASImageCacheProtocol, ASImageDownloaderProtocol>?

@end

```

#import "JSWebImageManager.h"

@implementation JSWebImageManager


- (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion{
??? @autoreleasepool {
??????? YYWebImageManager *manager = [YYWebImageManager sharedManager];
??????? __weak YYWebImageOperation *operation = nil;
??????? operation = [manager requestImageWithURL:URL
???????????????????????????????????????? options:YYWebImageOptionSetImageWithFadeAnimation
??????????????????????????????????????? progress:^(NSInteger receivedSize, NSInteger expectedSize) {
???????????????????????????????????????????
??????????????????????????????????????? }
?????????????????????????????????????? transform:nil
????????????????????????????????????? completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
????????????????????????????????????????? completion(image, error, operation);
????????????????????????????????????? }];
??????? return operation;
??? }
}

- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier {
??? if (![downloadIdentifier isKindOfClass:[YYWebImageOperation class]]) {
??????? return;
??? }
??? [(YYWebImageOperation *)downloadIdentifier cancel];
}

- (void)cachedImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion {
??? [self.cache getImageForKey:[self cacheKeyForURL:URL] withType:(YYImageCacheTypeAll) withBlock:^(UIImage * _Nullable image, YYImageCacheType type) {
??????? completion(image);
??????? if (image) {
??????????? dispatch_async(callbackQueue, ^{
??????????????? completion(image);
??????????? });
??????? } else {
??????????? dispatch_async(callbackQueue, ^{
??????????????? [self downloadImageWithURL:URL callbackQueue:callbackQueue downloadProgress:^(CGFloat progress) {
???????????????????
??????????????? } completion:^(id<ASImageContainerProtocol>? _Nullable image, NSError * _Nullable error, id? _Nullable downloadIdentifier) {
??????????????????? if (image) {
??????????????????????? completion(image);
??????????????????? }
??????????????? }];
??????????? });
??????? }
??? }];
}

@end

```

自定義JPNetworkImageNode(繼承自ASDisplayNode), 代替我們常用的ASNetworkImageNode,相關(guān)常用屬性如下

/** 網(wǎng)絡地址 */

@property (nonatomic, copy) NSURL *URL;

/** 轉(zhuǎn)場color */

@property (nonatomic, strong)UIColor *placeholderColor;

/** 靜態(tài)image */

@property (nonatomic, strong)UIImage *image;

/** 轉(zhuǎn)場時間 */

@property (nonatomic, assign)NSTimeInterval js_placeholderFadeDuration;

/** 空置圖片 */

@property (nonatomic, strong)UIImage *defaultImage;

/**
?網(wǎng)絡圖片
?*/
@property (nonatomic, strong) ASNetworkImageNode *netImgNode;
/**
?本地圖片
?*/
@property (nonatomic, strong) ASImageNode *imageNode;

```

#import "JSNetworkImageNode.h"
#import "JSWebImageManager.h"

@implementation JSNetworkImageNode

- (instancetype)init{
??? self = [super init];
??? if (self) {
??????? [self addSubnode:self.netImgNode];
??????? [self addSubnode:self.imageNode];
??? }
??? return self;
}

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
??? return [ASInsetLayoutSpec insetLayoutSpecWithInsets:(UIEdgeInsetsZero) child:!self.netImgNode.URL ? self.imageNode : self.netImgNode];
}

- (ASNetworkImageNode *)netImgNode{
??? if (!_netImgNode) {
??????? _netImgNode = [[ASNetworkImageNode alloc] initWithCache:JSWebImageManager.sharedManager downloader:JSWebImageManager.sharedManager];
??? }
??? return _netImgNode;
}

- (ASImageNode *)imageNode{
??? if (!_imageNode) {
??????? _imageNode = [[ASImageNode alloc] init];
??? }
??? return _imageNode;
}

- (void)setURL:(NSURL *)URL{
??? _URL = URL;
??? if ([YYImageCache.sharedCache containsImageForKey:[YYWebImageManager.sharedManager cacheKeyForURL:URL]]) {
??????? self.imageNode.image = [YYImageCache.sharedCache getImageForKey:[YYWebImageManager.sharedManager cacheKeyForURL:URL]];
??? } else {
??????? self.netImgNode.URL = _URL;
??? }
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor{
??? self.netImgNode.placeholderColor = placeholderColor;
}

- (void)setImage:(UIImage *)image{
??? self.netImgNode.image = image;
}

- (void)setDefaultImage:(UIImage *)defaultImage{
??? self.netImgNode.defaultImage = defaultImage;
}

- (void)setJs_placeholderFadeDuration:(NSTimeInterval)js_placeholderFadeDuration{
??? self.netImgNode.placeholderFadeDuration = js_placeholderFadeDuration;
}

@end



使用時將JPNetworkImageNode當做ASNetworkImageNode即可

2)reloadCell和reloadData引起的閃爍

當reloadASTableNode或者ASCollectionNode的某個indexPath的cell時憔涉,也會閃爍订框。原因和ASNetworkImageNode很像,都是異步惹的禍兜叨。當異步計算cell的布局時穿扳,cell使用placeholder占位(通常是白圖),布局完成時浪腐,才用渲染好的內(nèi)容填充cell纵揍,placeholder到渲染好的內(nèi)容切換引起閃爍顿乒。UITableViewCell因為都是同步议街,不存在占位圖的情況,因此也就不會閃璧榄。

這個官方給出的解決方案是:

?cellNode.neverShowPlaceholders = YES;

這樣設置以后特漩,會讓cell從異步加載衰退會同步狀態(tài),若reload某個indexPath的cell, 在渲染完成之前骨杂,主線程是卡死的涂身,這就和tableView原始的加載方式一樣了,但會比tableView速度快很多搓蚪,因為UITableView的布局計算蛤售、資源解壓、視圖合成等都是在主線程進行妒潭,而ASTableNode則是多個線程并發(fā)進行悴能,何況布局等還有緩存。但當頁面布局很多雳灾,刷新cell很多的時候漠酿,下拉掉幀就比較明顯,但我們知道ASTableNode具有預加載的相關(guān)設置谎亩,可以設置leadingScreensForBatching減緩卡頓炒嘲,但仍然不完美宇姚,時間換空間而已。我們要做到的是該異步的異步夫凸,又能不卡頓浑劳,又可以預加載。為此提供解決方案:

#import@interface ASTableNode (ReloadIndexPaths)

@property (nonatomic, copy) NSArray *js_reloadIndexPaths;//需要刷新的indexPath

@end

import "ASTableNode+reloadIndexPaths.h"

#importstatic void *strKey = &strKey;

@implementation ASTableNode (reloadIndexPaths)

- (void)setJs_reloadIndexPaths:(NSArray *)js_reloadIndexPaths{

? ? objc_setAssociatedObject(self, &strKey, js_reloadIndexPaths, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSArray *)js_reloadIndexPaths{

? ? return objc_getAssociatedObject(self, &strKey);

}

@end

在此對ASTableNode類目添加新的屬性js_reloadIndexPaths寸痢,需要刷新的indexPath

?ASCellNode *(^ASCellNodeBlock)(void) = ^ASCellNode *() {
??????? ImageCellNode *cellNode = [[ImageCellNode alloc] initWithModel:_viewModel.dataArray[indexPath.row]];
??????? if ([tableNode.js_reloadIndexPaths containsObject:indexPath]) {
??????????? cellNode.neverShowPlaceholders = YES;
??????????? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
??????????????? cellNode.neverShowPlaceholders = NO;
??????????? });
??????? } else {
??????????? cellNode.neverShowPlaceholders = NO;
??????? }
??????? return cellNode;
??? };
??? return ASCellNodeBlock;

reload單個indexPath

?_tableNode.js_reloadIndexPaths = @[indexPath];
?? [_tableNode reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimationNone)];

reload整個tableNode

_tableNode.js_reloadIndexPaths = _tableNode.indexPathsForVisibleRows;

[self.tableNode reloadData];

我們將需要刷新的indexPath放入js_reloadIndexPaths, 加以判斷設置該indexPath回歸主線程呀洲,當渲染完畢后再設置可以異步加載,0.5秒的時間足以渲染完畢啼止,這樣就完美實現(xiàn)該異步異步道逗,該同步同步,完美解決閃爍問題献烦。如絲般滑順滓窍。。巩那。

該文在原作者的基礎(chǔ)上加入了自己的理解吏夯,主要解決運用AsyncDisplayKit所導致的閃爍問題,歡迎大家提出問題即横,共同交流噪生。

提示:由于個人對源碼的實驗分析, 導致原來下載崩潰(現(xiàn)在已經(jīng)不存在該問題), 可在ImageCellNode.m中將_imageNode.view.contentMode = UIViewContentModeScaleAspectFill;該行注釋掉.主要是該方法必須在主線程中運行, 如果想更改該屬性, 可在didload方法中調(diào)整;最新demo 地址鏈接:demo? 密碼:aq6n

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市东囚,隨后出現(xiàn)的幾起案子跺嗽,更是在濱河造成了極大的恐慌,老刑警劉巖页藻,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桨嫁,死亡現(xiàn)場離奇詭異,居然都是意外死亡份帐,警方通過查閱死者的電腦和手機璃吧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废境,“玉大人畜挨,你說我怎么就攤上這事∝迹” “怎么了巴元?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栓始。 經(jīng)常有香客問我务冕,道長,這世上最難降的妖魔是什么幻赚? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任禀忆,我火速辦了婚禮臊旭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘箩退。我一直安慰自己离熏,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布戴涝。 她就那樣靜靜地躺著滋戳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪啥刻。 梳的紋絲不亂的頭發(fā)上奸鸯,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音可帽,去河邊找鬼娄涩。 笑死,一個胖子當著我的面吹牛映跟,可吹牛的內(nèi)容都是我干的蓄拣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼努隙,長吁一口氣:“原來是場噩夢啊……” “哼球恤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荸镊,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤咽斧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贷洲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體收厨,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡晋柱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年优构,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雁竞。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钦椭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碑诉,到底是詐尸還是另有隱情彪腔,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布进栽,位于F島的核電站德挣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏快毛。R本人自食惡果不足惜格嗅,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一番挺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屯掖,春花似錦玄柏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绍坝,卻和暖如春徘意,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轩褐。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工映砖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灾挨。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓邑退,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劳澄。 傳聞我的和親對象是個殘疾皇子地技,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 異步方法調(diào)用 異步方法調(diào)用或異步方法模式是(多線程)面向?qū)ο蟪绦蛟O計中用于異步調(diào)用對象的潛在的長期運行方法的一種設...
    路仟閱讀 525評論 0 0
  • react導入依賴 react由兩部分組成: react 包和 react-dom ,語法都是ES6 import...
    Nevermind閱讀 148評論 0 0
  • 上班 1.volte word文檔的書寫 收獲 1.vuex module 2.promise3.axios4.a...
    王zm閱讀 161評論 0 1
  • 一:《問佛》 佛祖,貪欲秒拔,糾纏于夢醒時分 那些求而不得的癡心妄想 在一呼一吸之間莫矗,游離存活 念念不忘的名利,在漩渦...
    一泓夜雨閱讀 317評論 0 0
  • adb devices時出現(xiàn)如下提示:no permissions (verify udev rules); se...
    zhujunhua閱讀 293評論 0 0