美文网首页
使用Time Profiler给K线图做的一次优化

使用Time Profiler给K线图做的一次优化

作者: 逗留 | 来源:发表于2017-11-21 15:50 被阅读44次

    Time Profiler(时间分析器)

    用来检测app执行过程中每个方法所用的时间


    image.png

    然后就可以看到每段代码所执行的时间了,点击右侧调用的方法,还可以跳转到代码显示更加直观详细。


    image.png

    优化部分:
    前提是已经使用leaks查看过,没有内存泄漏。另外听说layer是使用GPU的,而且苹果爸爸做了优化,所以将各种绘图转成各种layer:CAShapeLayer、CATextLayer、CAGradientLayer,然而,并无卵用,因为啥呢,因为这里卡顿的原因更多是耗时计算,而不是排版绘图,跟用不用layer并没啥亲戚关系,另外创建layer又是一大片对象,好像有点得不偿失。
    由于项目是金融类软件,要命的k线图加上手势,性能差得不要不要的。
    通过该工具,可以看到每次滑动屏幕,计算量大概要用1s的时间,我的天,难怪fps是个位数。。。
    既然如此耗时,首先想到的是,把计算过程放到子线程中去,效果很明显,fps瞬间飙到了40。可惜依然是不舒服,fps虽然上来了,但是延迟问题比较严重,图像变化总是慢半拍。
    检查哪个方法耗时比较多

    • NSDateFormatter
    + (NSString *)formatTimeFrom:(NSDate *)date withType:(YJStockType)type
    {
        NSDateFormatter *formatter = [NSDateFormatter new];
    
        switch (type) {
            case YJStockTypeEachMinute:
                [formatter setDateFormat:@"HH:mm"];
                break;
            case YJStockTypeFiveDay:
                [formatter setDateFormat:@"MM-dd HH:mm"];
                break;
            case YJStockTypeMonth:
                [formatter setDateFormat:@"yyyy-MM"];
                break;
            case YJStockTypeDay:
                [formatter setDateFormat:@"yyyy-MM-dd"];
                break;
            default:
                [formatter setDateFormat:@"yyyy-MM-dd HH:mm"];
                break;
        }
        return [formatter stringFromDate:date];
    }
    
    

    这家伙尽然能用到544ms,厉害厉害。开始想到的是使用YYModel里的做法

    /// Parse string to date.
    static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
        typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
        #define kParserNum 34
        static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            {
                /*
                 2014-01-20  // Google
                 */
                NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter.dateFormat = @"yyyy-MM-dd";
                blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; };
            }
            
            {
                /*
                 2014-01-20 12:24:48
                 2014-01-20T12:24:48   // Google
                 2014-01-20 12:24:48.000
                 2014-01-20T12:24:48.000
                 */
                NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
                formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
                
                NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    
                NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
                formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
    
                NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
                formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
                
                blocks[19] = ^(NSString *string) {
                    if ([string characterAtIndex:10] == 'T') {
                        return [formatter1 dateFromString:string];
                    } else {
                        return [formatter2 dateFromString:string];
                    }
                };
    
                blocks[23] = ^(NSString *string) {
                    if ([string characterAtIndex:10] == 'T') {
                        return [formatter3 dateFromString:string];
                    } else {
                        return [formatter4 dateFromString:string];
                    }
                };
            }
            
            {
                /*
                 2014-01-20T12:24:48Z        // Github, Apple
                 2014-01-20T12:24:48+0800    // Facebook
                 2014-01-20T12:24:48+12:00   // Google
                 2014-01-20T12:24:48.000Z
                 2014-01-20T12:24:48.000+0800
                 2014-01-20T12:24:48.000+12:00
                 */
                NSDateFormatter *formatter = [NSDateFormatter new];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
    
                NSDateFormatter *formatter2 = [NSDateFormatter new];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    
                blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; };
                blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; };
                blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; };
                blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
                blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
            }
            
            {
                /*
                 Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter
                 Fri Sep 04 00:12:21.000 +0800 2015
                 */
                NSDateFormatter *formatter = [NSDateFormatter new];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
    
                NSDateFormatter *formatter2 = [NSDateFormatter new];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
    
                blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; };
                blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
            }
        });
        if (!string) return nil;
        if (string.length > kParserNum) return nil;
        YYNSDateParseBlock parser = blocks[string.length];
        if (!parser) return nil;
        return parser(string);
        #undef kParserNum
    }
    

    后来一想人家这是为了尽量满足各种奇葩后台而准备的,自己的没有这么复杂的需求,干脆一点用内存代替计算,在模型中添加一个字典

    @property (nonatomic, strong) NSDate *time;
    
    @property (nonatomic, assign, readonly) NSInteger month;
    @property (nonatomic, assign, readonly) NSInteger year;
    @property (nonatomic, assign, readonly) NSInteger day;
    @property (nonatomic, assign, readonly) NSInteger week;
    @property (nonatomic, assign, readonly) NSInteger hour;
    @property (nonatomic, assign, readonly) NSInteger minute;
    @property (nonatomic, assign, readonly) NSInteger second;
      
    @property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSString *> *formatTimes;
    

    重写setTime方法,在获取到不同的time的时候,为各个时间组件赋值,这些本来业务逻辑也会用到。

    - (void)setTime:(NSDate *)time
    {
        _time = time;
        {
            NSCalendar *canlendar = [NSCalendar currentCalendar];
            NSDateComponents *components = [canlendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:time];
            _year = components.year;
            _month = components.month;
            _day = components.day;
            _week = components.weekday;
            _hour = components.hour;
            _minute = components.minute;
            _second = components.second;
        }
    }
    

    使用懒加载的方式添加formatTime

    + (NSString *)formatTimeFrom:(YJStockModel *)stock withType:(YJStockType)type
    {
        NSString *time = stock.formatTimes[@(type)];
        if (time) return time;
        
        switch (type) {
            case YJStockTypeEachMinute:
            time = [NSString stringWithFormat:@"%02zd:%02zd", stock.hour, stock.minute];
            break;
            case YJStockTypeFiveDay:
            time = [NSString stringWithFormat:@"%02zd-%02zd %02zd:%02zd", stock.month, stock.day, stock.hour, stock.minute];
            break;
            case YJStockTypeMonth:
            time = [NSString stringWithFormat:@"%zd-%02zd", stock.year, stock.month];
            break;
            case YJStockTypeDay:
            time = [NSString stringWithFormat:@"%zd-%02zd-%02zd", stock.year, stock.month, stock.day];
            break;
            default:
            time = [NSString stringWithFormat:@"%zd-%02zd-%02zd %02zd:%02zd", stock.year, stock.month, stock.day, stock.hour, stock.minute];
            break;
        }
        stock.formatTimes[@(type)] = time;
        return time;
    }
    

    第一次使用时候最多也就耗时几毫秒,后续使用几乎是不耗时的,一下子舒坦好多。

    • 通过几个模型计算而得的值
      比如MA10均线,每次都要从该模型开始,往前拿10个元素,然后计算收盘价的平均值,嗯,麻烦,还是老套路,内存换时间,同上。

    • 减少循环次数
      起初为了传说中的高内聚低耦合,把整个视图切成若干个部分,每个部分单独计算。这就造成了重复遍历数据模型,因为模型数据多到有几百个数据,多次重复遍历也是不好的,所以将遍历放到了最外面,遍历的时候再各个击破,也不算违背高内聚低耦合的思想吧。

    • 复用已经创建好的对象
      尽量避免对象的重复创建,如果已经创建的对象可以拿来复用,那自然是会好些。

    • 避免重复计算
      之前已经计算好的数据,在新数据来的时候,先判断前面的是否需要重新计算,如果不需要,则只计算新来的数据,拼接到之前的即可。

    通过上面的一系列操作之后,fps可以保持在55以上,虽然还有很多细节可以优化,懒惰的我已经对这个数值相当满意了,毕竟一开始是个位数。。。
    补充一点,测试真机为5s,现在来说应该属于低端机了,4系列这年月果断抛弃了,其实5系列都可以抛弃,优化以后,在7上fps是60,偶尔59,应该可以了吧,想当顺滑了!

    相关文章

      网友评论

          本文标题:使用Time Profiler给K线图做的一次优化

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