美文网首页
第十八篇: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