第十八篇:iOS界面优化

CPU负责计算 -- > GPU负责渲染 --> frameBuffer -- > video Controller --> Monitor

frameBuffer是存储帧缓存的,苹果是每隔60fps进行刷,有时无法同时刷到就会产生丢帧

  在YYKit框架里有检测FPS的,其主要是使用了CADisplayLink这个,点击进去是其绑定了一个vsync信号,其实际为60pfs,16.67ms,其实其是被绑定在link上面然后加入到一个runloop里。这里用到了YYWeakProxy是为了弱引用,添加中间变量,起到消息转发,防止循环引用。
@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];

//消息快速转发
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

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

//消息慢速转发
- (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++执行次数少了,那么就说明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这两种状态可以检测。首先就是获取当前线程的runloop状态,因为这个是主线程,应该是一直都会运行的,如果没运行的话在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting状态就说明线程阻塞了。

 if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
                {
              
                    if (++self->_timeoutCount < 2){
                        NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
                        continue;
                    }
                    NSLog(@"检测到超过两次连续卡顿 - %lu",(unsigned long)self->_timeoutCount);
                }

图片加载流程

1)预排版
就是先弄个framelayout进行先预排版

2)预编码/解码
图片加载流程
Data Buffer --->decoee-->Image Buffer --->Frame Buffer
一般采集到data后,通过开启子线程进行预解码操作(也就是把Data变成imageREF)

下采样可以优化内存:

- (UIImage *)downsampleImageAt:(NSURL *)imageURL to:(CGSize)pointSize scale:(CGFloat)scale {
    // 利用图像文件地址创建 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;
}

未使用下采样加载一个内存30M图片,消耗内存20.3MB如下

WechatIMG2166.jpeg

使用下采样加载一个内存30M图片,消耗内存13.6MB如下

WechatIMG2167.jpeg

3.按需加载
就是在滚动的时候不加载,停止后才进行加载

4.异步渲染
UIView是视图的显示和layer是用来渲染图层的,用drawRect操作,第三方框架Graver. 绘制到一张位图上面显示

  • (void)drawRect:(CGRect)rect {
    // Drawing code, 绘制的操作, BackingStore(额外的存储区域产于的) -- GPU
    }

我们再drawRect这里打个断点,然后进行bt指令调试,我们看到了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物理空间,但是他的优化也是很明显的。

为什么UI的渲染要在主线程,首先主线程比较安全,开发无法修改,在苹果底层是通过懒加载去操作的,主线程也是运行最快的,如果渲染放在异步那就会紊乱,一个这个界面中控件显示,另外一个显示其他的。

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

推荐阅读更多精彩内容

  • 卡顿的原理 想要进行界面优化,首先就要了解怎么产生卡顿?通常来说计算机中的显示过程是下面这样的,通过CPU、GPU...
    浅墨入画阅读 211评论 0 1
  • App 页面"卡顿“的原因是什么 在我们 iOS 开发的过程中,会遇到 APP 不流畅的情况。在屏幕图像显示的那些...
    _涼城阅读 959评论 0 5
  • 前言 我们经常在面试中,会被问及关于界面优化相关的问题,比如为什么界面会出现卡顿?如何监控卡顿?接着如何解决卡顿?...
    深圳_你要的昵称阅读 1,100评论 1 4
  • 在探讨iOS屏幕卡顿优化之前,首先我们来介绍屏幕成像的基本原理; CPU与GPU CPU:是计算机设备的运算中心与...
    YanZi_33阅读 675评论 0 1
  • 卡顿原因 计算机通过CPU、GPU、显示器三者协同工作将试图显示到屏幕上 1、CPU将需要显示的内容计算出来,提交...
    木扬音阅读 682评论 0 8