第十八篇:iOS界面優(yōu)化

CPU負責計算 -- > GPU負責渲染 --> frameBuffer -- > video Controller --> Monitor

frameBuffer是存儲幀緩存的,蘋果是每隔60fps進行刷,有時無法同時刷到就會產(chǎn)生丟幀

  在YYKit框架里有檢測FPS的,其主要是使用了CADisplayLink這個,點擊進去是其綁定了一個vsync信號,其實際為60pfs,16.67ms,其實其是被綁定在link上面然后加入到一個runloop里。這里用到了YYWeakProxy是為了弱引用,添加中間變量,起到消息轉發(fā),防止循環(huán)引用。
@implementation YYFPSLabel {
    CADisplayLink *_link;
    NSUInteger _count;
    NSTimeInterval _lastTime;
    UIFont *_font;
    UIFont *_subFont;
    
    NSTimeInterval _llll;
}

  _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
    [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

//消息快速轉發(fā)
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

//消息慢速轉發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}


/** Class representing a timer bound to the display vsync. **/

API_AVAILABLE(ios(3.1), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macOS)
@interface CADisplayLink : NSObject
{
@private
  void *_impl;
}

在代碼中 CGFloat progress = fps / 60.0,這里fps當小于60,說明count++執(zhí)行次數(shù)少了,那么就說明mian(主線程)被卡頓了。

  _count++;
    NSTimeInterval delta = link.timestamp - _lastTime;
    if (delta < 1) return;
    _lastTime = link.timestamp;
    float fps = _count / delta;
    _count = 0;
    
    CGFloat progress = fps / 60.0;
    UIColor *color = [UIColor colorWithHue:0.27 * (progress - 0.2) saturation:1 brightness:0.9 alpha:1];
    
    NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d FPS",(int)round(fps)]];
    [text setColor:color range:NSMakeRange(0, text.length - 3)];
    [text setColor:[UIColor whiteColor] range:NSMakeRange(text.length - 3, 3)];
    text.font = _font;
    [text setFont:_subFont range:NSMakeRange(text.length - 4, 1)];

Runloop檢測卡頓

線程和runloop是一一對應的

WechatIMG2139.jpeg
WechatIMG2140.jpeg

下面通過kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting這兩種狀態(tài)可以檢測。首先就是獲取當前線程的runloop狀態(tài),因為這個是主線程,應該是一直都會運行的,如果沒運行的話在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting狀態(tài)就說明線程阻塞了。

 if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
                {
              
                    if (++self->_timeoutCount < 2){
                        NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
                        continue;
                    }
                    NSLog(@"檢測到超過兩次連續(xù)卡頓 - %lu",(unsigned long)self->_timeoutCount);
                }

圖片加載流程

1)預排版
就是先弄個framelayout進行先預排版

2)預編碼/解碼
圖片加載流程
Data Buffer --->decoee-->Image Buffer --->Frame Buffer
一般采集到data后,通過開啟子線程進行預解碼操作(也就是把Data變成imageREF)

下采樣可以優(yōu)化內(nèi)存:

- (UIImage *)downsampleImageAt:(NSURL *)imageURL to:(CGSize)pointSize scale:(CGFloat)scale {
    // 利用圖像文件地址創(chuàng)建 image source
    NSDictionary *imageSourceOptions = @{(__bridge NSString *)kCGImageSourceShouldCache: @NO // 原始圖像不要解碼
    };
    CGImageSourceRef imageSource =
    CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, (__bridge CFDictionaryRef)imageSourceOptions);

    // 下采樣
    CGFloat maxDimensionInPixels = MAX(pointSize.width, pointSize.height) * scale;
    NSDictionary *downsampleOptions =
    @{
      (__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
      (__bridge NSString *)kCGImageSourceShouldCacheImmediately: @YES,  // 縮小圖像的同時進行解碼
      (__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform: @YES,
      (__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimensionInPixels)
       };
    CGImageRef downsampledImage =
    CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)downsampleOptions);
    UIImage *image = [[UIImage alloc] initWithCGImage:downsampledImage];
    CGImageRelease(downsampledImage);
    CFRelease(imageSource);

    return image;
}

未使用下采樣加載一個內(nèi)存30M圖片,消耗內(nèi)存20.3MB如下

WechatIMG2166.jpeg

使用下采樣加載一個內(nèi)存30M圖片,消耗內(nèi)存13.6MB如下

WechatIMG2167.jpeg

3.按需加載
就是在滾動的時候不加載,停止后才進行加載

4.異步渲染
UIView是視圖的顯示和layer是用來渲染圖層的,用drawRect操作,第三方框架Graver. 繪制到一張位圖上面顯示

  • (void)drawRect:(CGRect)rect {
    // Drawing code, 繪制的操作, BackingStore(額外的存儲區(qū)域產(chǎn)于的) -- GPU
    }

我們再drawRect這里打個斷點,然后進行bt指令調(diào)試,我們看到了commit_transaction這個方法操作。那commit_transaction做了什么呢?

WechatIMG2168.jpeg
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001001e99f8 LGViewRenderExplore`-[LGView drawRect:](self=0x000000014ad08490, _cmd="drawRect:", rect=(origin = (x = 0, y = 0), size = (width = 200, height = 200))) at LGView.m:20:1
    frame #1: 0x00000001852e9748 UIKitCore`-[UIView(CALayerDelegate) drawLayer:inContext:] + 552
    frame #2: 0x00000001001e9c48 LGViewRenderExplore`-[LGView drawLayer:inContext:](self=0x000000014ad08490, _cmd="drawLayer:inContext:", layer=0x0000600003065780, ctx=0x0000600000550480) at LGView.m:50:5
    frame #3: 0x00000001001e9964 LGViewRenderExplore`-[LGLayer display](self=0x0000600003065780, _cmd="display") at LGLayer.m:33:5
    frame #4: 0x000000018851e770 QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 400
    frame #5: 0x0000000188457538 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 448
    frame #6: 0x0000000188483564 QuartzCore`CA::Transaction::commit() + 696
    frame #7: 0x0000000184d991e8 UIKitCore`__34-[UIApplication _firstCommitBlock]_block_invoke_2 + 40
    frame #8: 0x0000000180360580 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 20
    frame #9: 0x000000018035f854 CoreFoundation`__CFRunLoopDoBlocks + 408
    frame #10: 0x000000018035a018 CoreFoundation`__CFRunLoopRun + 764
    frame #11: 0x0000000180359804 CoreFoundation`CFRunLoopRunSpecific + 572
    frame #12: 0x000000018c23660c GraphicsServices`GSEventRunModal + 160
    frame #13: 0x0000000184d7bd2c UIKitCore`-[UIApplication _run] + 992
    frame #14: 0x0000000184d808c8 UIKitCore`UIApplicationMain + 112
    frame #15: 0x00000001001e94b8 LGViewRenderExplore`main(argc=1, argv=0x000000016fc19c28) at main.m:17:12
    frame #16: 0x0000000100409cd8 dyld_sim`start_sim + 20
    frame #17: 0x00000001004990f4 dyld`start + 520
WechatIMG2169.jpeg

這種開異步渲染的話,會開啟多個線程,會占用cpu物理空間,但是他的優(yōu)化也是很明顯的。

為什么UI的渲染要在主線程,首先主線程比較安全,開發(fā)無法修改,在蘋果底層是通過懶加載去操作的,主線程也是運行最快的,如果渲染放在異步那就會紊亂,一個這個界面中控件顯示,另外一個顯示其他的。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 卡頓的原理 想要進行界面優(yōu)化,首先就要了解怎么產(chǎn)生卡頓?通常來說計算機中的顯示過程是下面這樣的,通過CPU、GPU...
    淺墨入畫閱讀 321評論 0 1
  • App 頁面"卡頓“的原因是什么 在我們 iOS 開發(fā)的過程中,會遇到 APP 不流暢的情況。在屏幕圖像顯示的那些...
    _涼城閱讀 1,138評論 0 5
  • 前言 我們經(jīng)常在面試中,會被問及關于界面優(yōu)化相關的問題,比如為什么界面會出現(xiàn)卡頓?如何監(jiān)控卡頓?接著如何解決卡頓?...
    深圳_你要的昵稱閱讀 1,477評論 1 4
  • 在探討iOS屏幕卡頓優(yōu)化之前,首先我們來介紹屏幕成像的基本原理; CPU與GPU CPU:是計算機設備的運算中心與...
    YanZi_33閱讀 971評論 0 4
  • 卡頓原因 計算機通過CPU、GPU、顯示器三者協(xié)同工作將試圖顯示到屏幕上 1、CPU將需要顯示的內(nèi)容計算出來,提交...
    木揚音閱讀 774評論 0 9

友情鏈接更多精彩內(nèi)容