iOS 性能优化(I)

作者: Harely | 来源:发表于2019-05-10 12:12 被阅读9次



    1. 如何对iOS设备进行性能测试?

    Xcode->Open Developer Tool -> Instruments ->Time Profiler
    

    测试介绍:App耗时方法检测



    2. 开发项目时你是怎么检查内存泄露?

    1). 静态分析 analyze。
    2). instruments工具里面有个leak可以动态分析。
    



    3. UITableView 如何进行优化?

    
    

    参考资料:tableView懒加载图片, tableview的优化,异步绘制cellUITableViewCell 性能优化



    4. 什么是懒加载?

    答:懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
    我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。
    



    5. UITableView重用机制?

    UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。
    



    6. 如何高性能的给 UIImageView 加个圆角?
    不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。

    self.view.layer.cornerRadius = 5.0f;
    self.view.layer.masksToBounds = YES;
    

    正确的解决方案:使用绘图技术

    - (UIImage *)circleImage {
        // NO代表透明
        UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
        // 获得上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        // 添加一个圆
        CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
        CGContextAddEllipseInRect(ctx, rect);
        // 裁剪
        CGContextClip(ctx);
        // 将图片画上去
        [self drawInRect:rect];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        // 关闭上下文
        UIGraphicsEndImageContext();
        return image;
    }
    

    还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    imageView.center = CGPointMake(200, 300);
    UIImage *anotherImage = [UIImage imageNamed:@"image"];
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                           cornerRadius:50] addClip];
    [anotherImage drawInRect:imageView.bounds];
    imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [self.view addSubview:imageView];
    



    7. 谈谈 UITableView 的优化

    1). 正确的复用cell。
    2). 设计统一规格的Cell
    3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
    4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
    4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
    5). 减少子视图的层级关系
    6). 尽量使所有的视图不透明化以及做切圆操作。
    7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
    8). 使用调试工具分析问题。
    
    



    8. 如何实行cell的动态的行高

    如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
    设置预估行高 tableView.estimatedRowHeight = 200。
    设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。 
    如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。
    



    9. 内存管理有哪几种?
    Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。

    1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
    2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
    3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

    有一个很经典的面试题,考察自动释放池的如下:

    for (int i = 0; i < MAXFLOAT; i++) {
    
            NSString *string = @"stdy";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"123"];
            NSLog(@"--%@", string);
        }
    

      上述的这种写法,会使内存慢慢增加,如何解决呢,面试官想要的答案就是用自动释放池,你也可以改成其他的,但不是面试官要的,你懂的,修改如下:

    for (int i = 0; i < MAXFLOAT; i++) {
            @autoreleasepool {
                NSString *string = @"stdy";
                string = [string lowercaseString];
                string = [string stringByAppendingString:@"123"];
                NSLog(@"--%@", string);
            }
        }
    

    9.1 什么时间会创建自动释放池?

      从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件,运行循环检测到事件并启动后,就会创建自动释放池。
      子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如:自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。

      但对于 blockOperationinvocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
    @autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
    如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。



    10. 什么会造成离屏渲染?

    GPU屏幕渲染有两种方式:
    (1)On-Screen Rendering (当前屏幕渲染)
    指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。
    (2)Off-Screen Rendering (离屏渲染)
    指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。
    

    下面的情况或操作会引发离屏渲染:

    • 为图层设置遮罩(layer.mask)
    • 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
    • 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
    • 为图层设置阴影(layer.shadow *)。
    • 为图层设置layer.shouldRasterize=true
    • 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
    • 文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
    • 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。

    优化方案

    1). 圆角优化
    方案1 :使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
    方案2 :使用CAShapeLayer和UIBezierPath设置圆角

    2). shadow优化
    对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能

    其他优化

      当我们需要圆角效果时,可以使用一张中间透明图片蒙上去使用ShadowPath指定layer阴影效果路径;
      使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)设置layer的opaque值为YES,减少复杂图层合成尽量使用不包含透明(alpha)通道的图片资源;
      尽量设置layer的大小值为整形值;
      直接让美工把图片切成圆角进行显示,这是效率最高的一种方案很多情况下用户上传图片进行显示,可以让服务端处理圆角使用代码手动生成圆角Image设置到要显示的View上;
      利用UIBezierPath(CoreGraphics框架)画出来圆角图片



    11. 说一下NSTimer在使用的时候内存泄漏的分析?

    NSTimer必须与RunLoop搭配使用,因为其定时任务的触发基于RunLoop。
    NSTimer使用常见的Target-Action模式。由于RunLoop会强引用timer,timer会强引用Target,容易造成循环引用、内存泄露等问题。
    



    12. loop 强引用timer, timer 强引用 target,如果不能释放,会造成内存泄漏,有一个面试官问如果在target中传入weak的self,那么可以解决循环引用问题吗?

    答案:否
    

      Target强引用or弱引用Timer并不是问题的关键,问题的关键是:一定要在Timer使用完毕调用invalidate使之失效(手动调用or系统自动调用),Timer从RunLoop中被移除并清除强引用,这个操作可打破引用1、2,而引用3是强弱引用已经不重要了。

      NSTimer一共有三种初始化方案:init开头的普通创建方法、timer开头的类工厂方法、scheduled开头的类工厂方法。前两者需要手动加入RunLoop中,后者会自动加入当前RunLoop的DefaultMode中。



    13. 对于NSTimer,面试官还会问,它是否是时间准确呢?
      大家可能都知道是时间不准确的,因为受RunLoop的影响,那么GCD中也有延时,如果用GCD来做延时,那时间准确吗?

    GCD的time是准确的,GCD 的线程管理是通过系统来直接管理的。
    GCD Timer 是通过 dispatch port 给 RunLoop 发送消息,来使 RunLoop 执行相应的 block。
    如果所在线程没有 RunLoop,那么 GCD 会临时创建一个线程去执行 block,执行完之后再销毁掉,因此 GCD 的 Timer 是不依赖 RunLoop 的。
    



    14. 大次数循环内存暴涨问题

    
    for (int i = 0; i < 100000; i++) {
      NSString *string = @“Abc”;
      string = [string lowercaseString];
      string = [string stringByAppendingString:@“xyz”];
      NSLog(@"%@", string);
    }
    改:
    for (int i = 0; i < 100000; i++) {
      @autoreleasepool {
        NSString *string = @“Abc”;
        string = [string lowercaseString];
        string = [string stringByAppendingString:@“xyz”];
        NSLog(@"%@", string);
      }
    }
    
    
    



    15. 内存泄漏可能会出现的几种原因,聊聊你的看法?
    15.1 非OC对象如何处理?
    15.2 地图类内存若泄漏,如何处理?
    15.3 若常用框架出现内存泄漏如何处理?
    iOS 出现内存泄漏的几种原因



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    



    ``

    
    

    相关文章

      网友评论

        本文标题:iOS 性能优化(I)

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