美文网首页
第十八篇:iOS界面优化

第十八篇:iOS界面优化

作者: 坚持才会看到希望 | 来源:发表于2022-06-19 23:16 被阅读0次

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

相关文章

网友评论

      本文标题:第十八篇:iOS界面优化

      本文链接:https://www.haomeiwen.com/subject/dkncvrtx.html