iOS 性能优化

作者: iOS104 | 来源:发表于2017-03-15 23:43 被阅读135次

    列举在项目开发中可能遇到的优化点

    • 1、内存泄露检测

      • 可能出现内存泄露的地方:循环引用,block强引用,NSTimer释放不当,NSNotification remove 监听,Core Foundation
    • 静态检查 Product -> Analyze 使用静态检查可以检查出一些明显没有释放的内存,CF开头的

    • instruments 工具检测。

    • 推荐一个比较好的工具MLeakFinder。比通过Instrument工具,不断尝试才得以定位好用的多,可以clone下源码看看基本原理。

    • 基本原理:为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。

    - (BOOL)willDealloc {
        __weak id weakSelf = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf assertNotDealloc];
        });
        return YES;
    }
    - (void)assertNotDealloc {
         NSAssert(NO, @“”);
    }
    
    • 2、FPS性能监测

      • 要保持流畅的UI交互,App 刷新率应该当努力保持在 60fps。监控实现原理比较简单,通过记录两次刷新时间间隔,就可以计算出当前的 FPS。利用CADisplayLink和NSRunLoop
        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayAction)];
        [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        
        - (void)displayAction {
        if (_lastTime == 0) {
            _lastTime = _link.timestamp;
            return;
        }
        
        _count++;
        NSTimeInterval delta = _link.timestamp - _lastTime;
        
        if (delta < 1) return;
        
        _lastTime = _link.timestamp;
        
        float fps = _count / delta;
        _count = 0;
    
        
        _fps = (int)round(fps);
        
        }
    
    
    • 3、NSDateFormatter
      • 设置一个全局的NSDateFormatter单例对象,用串行队列确保线程安全
    dispatch_queue_t formatterQueue = dispatch_queue_create("formatter queue", NULL);
    NSDateFormatter *dateFormatter;
    // ...
    - (NSDate *)dateFromString:(NSString *)string
    {
        __block NSDate *date = nil;
        dispatch_sync(formatterQueue, ^{
            date = [dateFormatter dateFromString:string];
        });
        return date;
    }
    
    • 4、主线程卡顿监控
      • 通过子线程监测主线程的 runLoop,判断两个状态区域之间的耗时是否达到一定阈值。
      • NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿.
    static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        MyClass *object = (__bridge MyClass*)info;
        
        // 记录状态值
        object->activity = activity;
        
        // 发送信号
        dispatch_semaphore_t semaphore = moniotr->semaphore;
        dispatch_semaphore_signal(semaphore);
    }
    - (void)registerObserver
    {
        CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                                kCFRunLoopAllActivities,
                                                                YES,
                                                                0,
                                                                &runLoopObserverCallBack,
                                                                &context);
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        
        // 创建信号
        semaphore = dispatch_semaphore_create(0);
        
        // 在子线程监控时长
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            while (YES)
            {
                // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
                long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
                if (st != 0)
                {
                    if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)
                    {
                        if (++timeoutCount < 5)
                            continue;
                        
                        NSLog(@"好像有点儿卡哦");
                    }
                }
                timeoutCount = 0;
            }
        });
    }
    
    /*
         第1个参数: 指定如何给observer分配存储空间
         第2个参数: 需要监听的状态类型/ kCFRunLoopAllActivities监听所有状态
         第3个参数: 是否每次都需要监听
         第4个参数: 优先级
         第5个参数: 监听到状态改变之后的回调
         */
        CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"即将进入runloop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"即将处理timer");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"即将处理source");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"即将进入睡眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"刚从睡眠中唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"即将退出");
                    break;
                default:
                    break;
            }
        });
        
        
        ```
    - 5、使用Autorelease Pool
     - 在循环中,如果有很多autorelease对象,要放在循环内部。
    - 6、UIImage缓存取舍
     - imagedNamed 默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.
     - tableView中或者反复使用的图片可以使用imageNamed
     - imageWithContentsOfFile 则仅只加载图片,不缓存。一次性大图片使用
    - 7、加速启动时间
        - 程序启动时,除了界面上的操作,其他尽可能放在工作线程做。一些不重要的任务可以延后执行,例如判断版本升级提醒
    - 8、UIKit 优化
        - 1、尽量使用不透明的View opaque为YES
        - 2、ImageView 使用合适大小的图片资源
        - 3、重用和延迟加载Views
        - 4、尽可能在TableViewCell时避免设置圆角,如果必须大量设置圆角,UIImageView 的圆角通过直接截取图片实现。其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。少量的话使用cornerRadius
        - 5、使用shadowPath来画阴影
        - 6、减少subviews的数量
        - 7、缓存行高
    - 9、日志打点
        - 项目中有些业务异常需要在一定的环境才能重新,最好就好做好日志打点,预留埋点,在出现问题的时候方便排除问题
        - 出现问题的时候网速监控上报
    - 10、合理的线程分配
        - 开的线程越来越多,线程的开销逐渐明显。
        - DB操作,日志操作,网络回调在各自固定的线程。不同业务,通过创建队列保持数据的一致性。
        - 主线程尽量少处理非UI的操作,同时控制整个APP子线程数量在一个合理的范围内。
    - 11、建立合适的DB索引
    - 12、cache
        - cache可能是所有性能优化中最常用的手段,但是cache建立的成本低,见效快,但是带来维护的成本却很高
        - 并发访问 cache 时,数据一致性问题
        - cache 线程安全问题
    - 13、基本类的抽取
        - 基本类抽取可以提高开发效率
        - 工具类,比如View,Label,Button的快捷创建
    - 14、Unit Tests 和 UI Testing 的使用

    相关文章

      网友评论

        本文标题:iOS 性能优化

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