Time Profiler(时间分析器)
用来检测app执行过程中每个方法所用的时间
然后就可以看到每段代码所执行的时间了,点击右侧调用的方法,还可以跳转到代码显示更加直观详细。
优化部分:
前提是已经使用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,应该可以了吧,想当顺滑了!