一個(gè)iOS性能優(yōu)化組件

簡(jiǎn)介

  • LNAsyncKit是一個(gè)異步渲染工具,它提供了便捷的方法幫助你將多個(gè)元素(Element)異步渲染到一張圖片上,讓這個(gè)過程代替UIKit的視圖構(gòu)建過程,進(jìn)而優(yōu)化App性能;Prender提供預(yù)加載策略幫助你在Feed流中彌補(bǔ)異步渲染帶來的延時(shí);除構(gòu)建視圖外,Transaction提供更優(yōu)雅的方式讓主線程與子線程交互,并能根據(jù)機(jī)器狀態(tài)控制并發(fā)數(shù)和主線程回調(diào)時(shí)機(jī)。

  • LNAsyncKit借(ji)鑒(cheng)了很多YYKit和Texture,如果對(duì)它們不是很了解可以戳這個(gè)比較詳細(xì)的文章,這篇文章的作者是YY大神:iOS保持頁(yè)面流暢的技巧。流暢性優(yōu)化的思想基本上都如這篇文章所述。

它可以提供哪些幫助

  • 還沒有找到方案優(yōu)化圓角、邊框、漸變的優(yōu)化方案,LNAsyncKit可以異步解決這些。
  • Feed流需要預(yù)加載策略,LNAsyncKit提供預(yù)加載區(qū)域計(jì)算方案(這個(gè)方案也用來預(yù)合成)。
  • 提供一種與UIKit十分接近的方式構(gòu)建需要預(yù)合成的圖層,讓你的復(fù)雜圖層構(gòu)建都放在子線程進(jìn)行,且不會(huì)創(chuàng)建那么多UIView。
  • Demo展示了使用:AFNetworking/SDWebImage/IGListKit/YYModel/MJRefresh + LNAsyncKit搭建feed流的方法。除去LNAsyncKit,前面5個(gè)構(gòu)成的這套體系已經(jīng)比較完整,Demo中也提供了沒有使用LNAsyncKit構(gòu)建的Feed。因此,需要快速學(xué)習(xí)如何搭建一套Feed流的初學(xué)者可以參考這套三方。

Github鏈接

你可以直接下載這個(gè)鏈接并運(yùn)行上面的Demo參考代碼實(shí)現(xiàn)自己的異步列表,也可以直接使用Cocoapods??

pod 'LNAsyncKit'

流暢性優(yōu)化

網(wǎng)絡(luò)上已經(jīng)有了很多流暢性優(yōu)化的文章,再逐一復(fù)述這些優(yōu)化點(diǎn)意義不大;這個(gè)文章是為了表達(dá)如何在Feed流中實(shí)現(xiàn)那些優(yōu)化思想,并把這個(gè)過程簡(jiǎn)化;所以,不再贅述這些優(yōu)化點(diǎn)為什么好、好多少,只談怎么實(shí)現(xiàn)它們;如果對(duì)這些優(yōu)化點(diǎn)有疑問可以參考上面鏈接的文章,以下這些觀點(diǎn)成立:

  • 圖層少的列表比圖層多的列表好。
  • 沒有圓角、邊框、漸變等復(fù)雜圖層的比有的好。
  • 圖片尺寸和控件尺寸一樣大的好。
  • 模型解析放在子線程比放在主線程好。
  • 布局計(jì)算放在子線程比放在主線程好。
  • 有預(yù)加載比沒有預(yù)加載好(見仁見智,也有喜歡無(wú)預(yù)加載列表的)。
  • Layer比View好(無(wú)手勢(shì)時(shí))。
  • 不透明圖層比透明圖層好。

在業(yè)務(wù)復(fù)雜度不變的前提下讓這些優(yōu)化工作變簡(jiǎn)單、自由就是LNAsyncKit的目標(biāo)。

優(yōu)化一個(gè)Cell

我們將一個(gè)Cell視為Feed流的最小優(yōu)化單元,以一個(gè)Bilibili推薦Feed流中一個(gè)常規(guī)的Cell為例:

這樣一個(gè)小Cell中包含了:封面圖、人數(shù)圖標(biāo)、人數(shù)Label、主播昵稱、直播間名、[直播]、直播內(nèi)容分類、負(fù)反饋按鈕8個(gè)元素;除了這些元素外,還包括封面圖底部一個(gè)黑色漸變的圖層、[直播]的圓角、邊框和整個(gè)Cell的圓角(好像還有些陰影);這個(gè)小Cell已經(jīng)包含比較多的小元素了,我們?cè)贒emo中嘗試復(fù)原一下并查看視圖層級(jí)大致如下:

具體構(gòu)建代碼這里不贅述了,使用LNAsyncKit可以簡(jiǎn)化這個(gè)Cell為如下這個(gè)樣子:

(右下角反饋Bug需要響應(yīng)事件,通常這種控件會(huì)保持獨(dú)立)

以“直播”標(biāo)簽為例,視圖構(gòu)建方式區(qū)別如下:

UIKit:

    self.liveTagLabel.layer.cornerRadius = 3.f;
    self.liveTagLabel.layer.borderColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f].CGColor;
    self.liveTagLabel.layer.borderWidth = 1.f;
    self.liveTagLabel.text = @"直播";
    self.liveTagLabel.font = [UIFont systemFontOfSize:12.f];
    self.liveTagLabel.textColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    self.liveTagLabel.textAlignment = NSTextAlignmentCenter;
    [self.cellContentView addSubview:self.liveTagLabel];

LNAsyncKit:

    LNAsyncTextElement *liveTagElement = [[LNAsyncTextElement alloc] init];
    liveTagElement.cornerRadius = 3.f;
    liveTagElement.borderColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    liveTagElement.borderWidth = 1.f;
    liveTagElement.text = @"直播";
    liveTagElement.font = [UIFont systemFontOfSize:12.f];
    liveTagElement.textColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    liveTagElement.textAligment = NSTextAlignmentCenter;
    [cellContentElement addSubElement:liveTagElement];

經(jīng)過LNAsyncKit渲染出與需要展示視圖面積一樣大的一張完整圖片,復(fù)雜渲染邏輯全部被子線程消化,反饋到主線程只表現(xiàn)為一張與目標(biāo)控件大小一致的圖片。

原理

與UIKit類似,LNAsyncKit也使用視圖樹構(gòu)建最終視圖。區(qū)別是:

A. Element繼承自NSObject,這些Element可以在子線程創(chuàng)建、渲染、銷毀??梢詫lement理解為“一個(gè)需要繪制圖層”的描述物,它并不是一個(gè)實(shí)體,它與UIView/CALayer的區(qū)別就好像:UIView是你要買的一件物品;Element則是下單信息,里面包含這件物品的各種描述信息,多大、什么顏色等。

B. 所有的Element都是臨時(shí)的,這些信息在構(gòu)建出結(jié)果后就會(huì)被銷毀,你可以在進(jìn)入子線程之后創(chuàng)建這些Element,在渲染出真正的圖片后銷毀這些Element,然后在主線程返回需要的圖片,像這樣:

    dispatch_queue_t queue = dispatch_queue_create(0, 0);
    dispatch_async(queue, ^{
        LNAsyncElement *contentElement = [weakSelf rebuildElements];
        [LNAsyncRenderer traversalElement:contentElement];
        UIImage *image = contentElement.renderResult;
        contentElement.renderResult = nil;
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.imageView.image = image;
        });
    });

rebuildElement的過程可以構(gòu)建出很復(fù)雜的一棵樹,但對(duì)主線程來說,這并不會(huì)造成問題!不在主線程出現(xiàn)Element也是LNAsyncKit推薦的使用方法(拿到resultImage后,把Element.resultImage置為空),當(dāng)然,出現(xiàn)了一般也無(wú)所謂,NSObject的消耗相對(duì)于UIView來講是很小的。

C.Element是逐層渲染的:實(shí)際上是后續(xù)遍歷,把A的子Element先渲染出來,然后渲染A,再把A當(dāng)做一個(gè)子節(jié)點(diǎn)渲染父節(jié)點(diǎn),LNAsyncRendererTraversalStack就是遍歷時(shí)使用的棧、LNAsyncRenderer.traversal函數(shù)是遍歷方法。遍歷中自帶了環(huán)檢測(cè),不會(huì)渲染重復(fù)Element,像這樣:

    LNAsyncRendererTraversalStack *stack = [[LNAsyncRendererTraversalStack alloc] init];
    [stack pushElements:@[element]];
    
    NSMutableSet <LNAsyncElement *> *repeatDetectMSet = [[NSMutableSet alloc] init];
    while (!stack.isEmpty) {
        LNAsyncElement *topElement = [stack top];
        if (topElement.getSubElements.count > 0 && (![repeatDetectMSet containsObject:topElement])) {
            [repeatDetectMSet addObject:topElement];
            [stack pushElements:topElement.getSubElements.reverseObjectEnumerator.allObjects];
        } else {
            [stack pop];
            [self renderElement:topElement];
            for (LNAsyncElement *subElement in topElement.getSubElements) {
                subElement.renderResult = nil;
            }
        }
    }

LNAsync自帶了一些Element:

  • LNAsyncElement: 對(duì)應(yīng)于UIKit的UIView,是其他Element的基類,包含了背景色、frame、和常用的邊界、圓角等屬性。
  • LNASyncImageElement: 對(duì)應(yīng)于UIImageView,渲染一張圖片、提供三種填充方式。
  • LNAsyncTextElement: 對(duì)應(yīng)于UILabel,渲染一段文字,提供常規(guī)文字屬性、支持折行。
  • LNAsyncLinerGradientElement: 對(duì)應(yīng)于CAGradientLayer,渲染一段漸變色。

自定義Element:

除原生Element外,我們也推薦封裝自己的Element,例如:一個(gè)AvatarElement,可以將用戶頭像、VIP標(biāo)識(shí)、頭像邊框等修飾物渲染在一起,重寫- (void)renderSelfWithContext:(CGContextRef)context,在這個(gè)方法中分別繪制這三個(gè)元素。

自定義Element的意義在于,所有自定義過的Element都是可復(fù)用、可組合的,這樣方便保持整個(gè)App風(fēng)格統(tǒng)一,也會(huì)適當(dāng)減少開發(fā)成本。

Feed流

上面我們已經(jīng)介紹過單個(gè)Cell、單張圖片如何異步渲染以優(yōu)化性能,但性能問題往往不是單張圖片所能引發(fā),LNAsyncKit更傾向于性能敏感的場(chǎng)景:Feed流;渲染Feed流相比渲染單個(gè)視圖需要考慮的事情要多一些:Cell復(fù)用、渲染好的圖片緩存、多張圖片下載和結(jié)果合并等問題;除此之外,也考慮使用預(yù)加載、預(yù)渲染功能來優(yōu)化用戶體驗(yàn)。

使用到的三方庫(kù):

  • AFNetworking 網(wǎng)絡(luò)
  • IGListKit Feed流框架,可以拆分各個(gè)模塊業(yè)務(wù)
  • SDWebImage 圖片下載
  • YYModel 字典轉(zhuǎn)模型
  • MJRefresh 上拉/下拉刷新組件
  • 一位大佬寫的免費(fèi)API ,雖然我不認(rèn)識(shí)這位大佬,但這些接口確實(shí)非常方便,在這里面朝空氣感謝一下~

這些都是非常成熟的三方框架,直接拿來用會(huì)減少不少開發(fā)時(shí)間;這里主要是介紹如何將LNAsyncKit融進(jìn)這個(gè)體系中去。Demo中已經(jīng)提供了默認(rèn)的Feed流和異步的Feed流代碼,如果遇到了一些奇怪的Bug可以參考Demo中的實(shí)現(xiàn),目前這兩個(gè)Demo都可以正常運(yùn)行。

  • 默認(rèn)Demo:我們用此Demo展示一個(gè)常規(guī)Feed流實(shí)現(xiàn)過程,沒有使用任何修飾手法或設(shè)計(jì)思想,可以理解為實(shí)現(xiàn)一個(gè)Feed流所需要做的最少工作。
  • 異步Demo:我們用此Demo將使用LNAsyncKit實(shí)現(xiàn)Feed流時(shí)與通常情況下的實(shí)現(xiàn)的進(jìn)行對(duì)比,了解從普通Feed轉(zhuǎn)異步Feed的修改點(diǎn)和差異之處。

默認(rèn)Feed流實(shí)現(xiàn):

  1. ViewDidLoad中使用AFNetworking請(qǐng)求一頁(yè)數(shù)據(jù),使用YYModel解析成Model類型數(shù)據(jù),賦值給VC。
  2. VC調(diào)用CollectionView/IGList刷新列表,將Model賦值到Cell內(nèi)部。
  3. Cell內(nèi)部賦值懶加載的Label、ImageView調(diào)用sd_setImage下載圖片展示。

異步Feed流的優(yōu)化:

1.圖片下載放在Model中進(jìn)行

A.因?yàn)楫惒紽eed不僅僅需要下載圖片,也需要將多個(gè)原始圖片進(jìn)行預(yù)合成,所以這個(gè)過程在Model中進(jìn)行可以保證不會(huì)因Cell復(fù)用問題導(dǎo)致同一時(shí)間合成多次,如果你在Cell中異步進(jìn)行圖層合成,那可能每次賦值Model都會(huì)合成一次,但在Model中合成后可以一直存放在Model中(Model只持有弱引用,存在全局的NSCache中)。

B.考慮預(yù)加載,我們認(rèn)為圖層的預(yù)加載和預(yù)合成是兩種優(yōu)先級(jí)的事情,通常距離屏幕焦點(diǎn)區(qū)域較遠(yuǎn)的區(qū)域只需要進(jìn)行圖片預(yù)下載,而距離較近的地方則需要預(yù)合成,不論是哪種方式,Cell通常只會(huì)在展示在屏幕上的時(shí)間點(diǎn)附近才能拿到,如果圖片下載放在Cell中進(jìn)行,是很難實(shí)現(xiàn)“預(yù)”的。

MVC中Model的職責(zé)之一是提供View展示需要的數(shù)據(jù),所以在Model中下載圖片并非錯(cuò)誤或不恰當(dāng)?shù)淖龇ā?/p>

2.模型解析和布局計(jì)算視為網(wǎng)絡(luò)請(qǐng)求的一部分

通常,在使用AFNetworking進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí),我們通常在成功回調(diào)中進(jìn)行模型解析和列表刷新,列表刷新時(shí)走CollectionView的dataSource協(xié)議計(jì)算布局。

異步列表不推薦這樣做:模型解析的過程沒有想象中的那樣簡(jiǎn)單,通常進(jìn)行模型解析時(shí)需要逐層遍歷Dictionary,然后創(chuàng)建大量Model和子Model,雖然單個(gè)NSObject開銷不大,但列表視圖的模型總是堆積起來的,創(chuàng)建如此多的對(duì)象也是個(gè)不小的開銷。

計(jì)算布局的耗時(shí)是公認(rèn)的,所以一般表視圖優(yōu)化都推薦緩存行高,但即便緩存行高,第一次在主線程中的計(jì)算也是有一定耗時(shí)的。

我們推薦在AFNetworking回調(diào)中異步進(jìn)行模型解析和布局計(jì)算,將這兩個(gè)操作視為網(wǎng)絡(luò)請(qǐng)求的一部分,這并不會(huì)對(duì)網(wǎng)絡(luò)請(qǐng)求的整體響應(yīng)時(shí)間有較大的影響,因?yàn)榫W(wǎng)絡(luò)回調(diào)時(shí)間單位通常要比屏幕刷新時(shí)間單位高出一個(gè)數(shù)量級(jí)。況且,預(yù)加載技術(shù)完全可以彌補(bǔ)這段小延時(shí)。

在請(qǐng)求回調(diào)中賦值給Model的LayoutObj就是對(duì)這個(gè)過程的封裝,像這樣:

- (void)transferFeedData:(NSDictionary *)dic comletion:(DemoFeedNetworkCompletionBlock)completion
{
    LNAsyncTransaction *transaction = [[LNAsyncTransaction alloc] init];
    
    [transaction addOperationWithBlock:^id _Nullable{
        DemoFeedModel *feedModel = [DemoFeedModel yy_modelWithDictionary:dic];
        for (DemoFeedItemModel *item in feedModel.result) {
            DemoAsyncFeedDisplayLayoutObjInput *layoutInput = [[DemoAsyncFeedDisplayLayoutObjInput alloc] init];
            layoutInput.contextString = item.title;
            layoutInput.hwScale = 0.3f + ((random()%100)/100.f)*0.5f; 
            DemoAsyncFeedDisplayLayoutObj *layoutObj = [[DemoAsyncFeedDisplayLayoutObj alloc] initWithInput:layoutInput];
            item.layoutObj = layoutObj;
        }
        return feedModel;
    } priority:1 queue:_transferQueue completion:^(id  _Nullable value, BOOL canceled) {
        if (completion) {
            completion(YES, value, nil);
        }
    }];
    
    [transaction commit];
}
3.在Model中布局

這聽起來有點(diǎn)詭異,在Model中下載圖片也就算了,為什么視圖操作也在Model中進(jìn)行?

我們已經(jīng)解釋了Element的職責(zé),它只是負(fù)責(zé)描述的類。使用element構(gòu)建視圖的過程就是:Model想好要怎么構(gòu)建(Element),把想法交付LNAsyncRenderer,renderer交付我們image,Model把image反回給View顯示出來。就像我們?cè)陂_始的時(shí)候講述的那樣。

4.預(yù)加載

預(yù)加載主要內(nèi)容包括兩個(gè)方面:預(yù)加載下一頁(yè)信息和預(yù)加載圖片。這里提到的預(yù)加載主要是指預(yù)加載圖片:

根據(jù)上面我們提到了圖片加載都是在Model中進(jìn)行的,所以,每個(gè)Model都需要一個(gè)必要的參數(shù)來標(biāo)記自身所持有的資源已經(jīng)到了那種緊急的程度了,如果距離當(dāng)前用戶焦點(diǎn)還很遠(yuǎn),說明自己的資源目前不是很緊急,可以先靜觀其變;如果距離用戶焦點(diǎn)有點(diǎn)近了,說明自己可能需要開始考慮先把圖片下載下來;如果距離用戶焦點(diǎn)已經(jīng)相當(dāng)近了,就要立刻開始準(zhǔn)備把已有資源預(yù)合成了。類似這樣:

- (void)setStatus:(DemoFeedItemModelStatus)status
{
    if (status > _status) {
        _status = status;
    }
    [self checkCurrentStatus];
}

- (void)checkCurrentStatus
{
    if (self.status >= DemoFeedItemModelStatusPreload) {
        //需要預(yù)加載圖片
        [self preloadImage];
    }
    
    if (self.status >= DemoFeedItemModelStatusDisplay) {
         //需要渲染視圖
         [self renderView];
    }
}

LNAsyncCollectionViewPrender提供了一套資源緊急程度標(biāo)記策略,將距離當(dāng)前屏幕中心較遠(yuǎn)的資源標(biāo)記為不緊急,較近的資源標(biāo)記為緊急,Model受緊急程度標(biāo)記影響自主進(jìn)行預(yù)加載或預(yù)渲染。

這套智能預(yù)加載機(jī)制來自于Texture,非常的實(shí)用,我將它修改為Objective-C實(shí)現(xiàn),并做了簡(jiǎn)化處理。你甚至可以參照這個(gè)區(qū)間計(jì)算思路制作一個(gè)滾動(dòng)列表曝光打點(diǎn)類,來計(jì)算那些更符合用戶視距的曝光區(qū)間,而不是僅僅簡(jiǎn)單依賴cell/View的生命周期,說到這不得不提一嘴:我曾經(jīng)見過一個(gè)埋點(diǎn)系統(tǒng)迭代了兩年多依然沒啥卵用。

5.圖片一致性校驗(yàn)

異步Cell渲染圖片回調(diào)設(shè)置圖片需要進(jìn)行渲染的模型與當(dāng)前模型是否一致的校驗(yàn),復(fù)用可能會(huì)導(dǎo)致一個(gè)Cell先后被設(shè)置兩個(gè)Model,這樣兩個(gè)Model在異步渲染結(jié)束后都可能通知Cell刷新數(shù)據(jù),所以需要一致性校驗(yàn)。同步不存在這個(gè)問題,后來的內(nèi)容總是會(huì)覆蓋掉先來的圖片。像這樣:

    NSObject *model = self.model;
    __weak DemoAsyncFeedCell *weakSelf = self;
    [self.model demoAsyncFeedItemLoadRenderImage:^(BOOL isCanceled, UIImage * _Nullable resultImage) {
        if (!isCanceled && resultImage && model == weakSelf.model) {
            weakSelf.contentView.layer.contents = (__bridge id)resultImage.CGImage;
        }
    }];
6.渲染緩存

與SDWebImage下載的原生Image不同,渲染后的圖片存儲(chǔ)在額外的一個(gè)渲染緩存中,Model弱引用持有,緩存內(nèi)部使用LRU管理;不能使用Model強(qiáng)引用,因?yàn)橛行〧eed流是常駐的,我們不希望內(nèi)存浪費(fèi)在不是主要消費(fèi)場(chǎng)景的常駐頁(yè)面中。LNAsyncCache是統(tǒng)一的存放的地方,你可以在渲染成功后把圖片存在這里,使用弱指針指向它,如果被刪除了,就重新渲染、存儲(chǔ)。

7.減少渲染次數(shù)

SD下載圖片時(shí)附帶AvoidDecode參數(shù),因?yàn)楹铣蛇^程會(huì)將Image渲染到一塊內(nèi)存中,這個(gè)過程本身就包含了解碼,且也是在子線程中進(jìn)行;使用這個(gè)參數(shù)可以減少圖片剛下載好時(shí)的那次渲染。像這樣:

[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:weakSelf.image]
                                            options:SDWebImageAvoidDecodeImage 
                                           progress:nil 
                                          completed:nil];

總結(jié)

LNAsyncKit優(yōu)化的內(nèi)容就如上所述:

  • 從主線程的角度來看:除了刷新CollectionView和計(jì)算預(yù)加載區(qū)域外基本上沒有耗時(shí)工作,布局計(jì)算和模型解析轉(zhuǎn)移到了子線程統(tǒng)一進(jìn)行,Element創(chuàng)建銷毀操作主線程基本上沒有感知。
  • 從CPU的角度來看:圓角、邊框、漸變等工作都在圖層合成的時(shí)候異步消化了,返回的圖片大小和Layer控件大小也是一致的,圖層的復(fù)雜層級(jí)也被子線程異步消化。
  • 從子線程角度看:子線程有很多。

寫異步Feed流比普通Feed流難度要稍微大一些,平均開發(fā)的時(shí)間成本也會(huì)有所上升;從效率上來講,每個(gè)需求的開發(fā)效率確實(shí)降低了,但這將會(huì)省去在未來單獨(dú)成立一個(gè)性能優(yōu)化小組進(jìn)行優(yōu)化的效率要高得多。平臺(tái)類型的開發(fā)人員往往沒有業(yè)務(wù)開發(fā)對(duì)業(yè)務(wù)更熟悉,因此需要頻繁交流確認(rèn)優(yōu)化點(diǎn)、改動(dòng)范圍、影響等等。而且,有時(shí)遇到優(yōu)化點(diǎn)時(shí)業(yè)務(wù)受限,可能不敢大刀闊斧地糾正,導(dǎo)致優(yōu)化后的結(jié)果和優(yōu)化前對(duì)比并不明顯。LNAsyncKit讓業(yè)務(wù)線從一開始做需求時(shí)就考慮到優(yōu)化內(nèi)容,從而省去了專項(xiàng)優(yōu)化的時(shí)間。當(dāng)然,如果App整體不考慮性能問題,選擇正常的開發(fā)方式就好。

雜談

iPhone手機(jī)硬件越來越強(qiáng),常規(guī)業(yè)務(wù)不進(jìn)行優(yōu)化一般也能達(dá)到流暢性標(biāo)準(zhǔn),端內(nèi)的卡頓只要不是特別嚴(yán)重產(chǎn)品經(jīng)理通常也都能接受;我在需求中使用了類似的方式進(jìn)行性能優(yōu)化,開發(fā)時(shí)間確實(shí)很緊。當(dāng)然,如果你的公司只考慮需求產(chǎn)出,他們通常不會(huì)給你這些時(shí)間,你可以在自己的編碼追求和實(shí)際情況之間決定是否要額外做這些事。

LNAsyncKit可以直接使用,也可以將它當(dāng)做你更深層次了解性能優(yōu)化、Texture的墊腳石;總之,它能起到任何幫助,我都將十分榮幸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

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