[性能优化]DateFormatter深度优化探索
前言
在iOS开发中,对日期进行格式化处理通常有三个步骤:
- 创建DateFormatter对象
- 设置日期格式
- 使用DateFormatter对象对日期进行处理
在上篇文章《DateFormatter性能优化》中,我们通过创建单例对象的方式对创建DateFormatter对象,设置日期格式两个步骤进行了缓存,将方法耗时降低为不缓存的方案的10%左右,但是这种优化方法受制于DateFormatter的几个系统方法的执行效率,本身具有一定的局限性。之前在一些文章中,也看到了使用C语言的
size_t strftime_l(char * __restrict, size_t, const char * __restrict, const struct tm * __restrict, locale_t) __DARWIN_ALIAS(strftime_l) __strftimelike(3);
函数对日期格式化进行处理,所以本文将对以下几种情况的方法耗时进行评测:
- 使用Objective-C,不缓存DateFormatter对象
- 使用Objective-C,缓存DateFormatter对象
- 使用Objective-C,调用strftime_l做日期处理
- 使用Swift,不缓存DateFormatter对象
- 使用Swift,缓存DateFormatter对象
- 使用Swift,调用strftime_l做日期处理
Objective-C的三种情况下的代码
//不缓存DateFormatter对象 -(void)testDateFormatterInOCWithoutCache:(NSInteger)times { NSString *string = @""; NSDate *date; CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); for (int i=0; i<times; i++) { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy年MM月dd日HH时mm分ss秒"]; date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)]; string = [dateFormatter stringFromDate:date]; } CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; NSLog(@"\n不缓存DateFormatter对象的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration); } //缓存DateFormatter对象 -(void)testDateFormatterInOCWithCache:(NSInteger)times { NSString *string = @""; NSDate *date; CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); for (int i=0; i<times; i++) { date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)]; string = [[DateFormatterCache shareInstance].formatterOne stringFromDate:date]; } CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; NSLog(@"\n缓存DateFormatter对象的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration); } //使用C语言来做日期处理 -(void)testDateFormatterInC:(NSInteger)times { NSString *string = @""; NSDate *date; time_t timeInterval; char buffer[80]; CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); for (int i=0; i<times; i++) { date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)]; timeInterval = [date timeIntervalSince1970]; strftime(buffer, sizeof(buffer), "%Y年%m月%d日%H时%M分%S秒", localtime(&timeInterval)); string = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; } CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; NSLog(@"==%@", string); NSLog(@"\n使用C语言的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration); }
这里对于咱们iOS开发的同学来说比较陌生的就是strftime_l(buffer, sizeof(buffer), "%Y年%m月%d日%H时%M分%S秒", localtime(&timeInterval), NULL);
这这行代码的调用,strftime_l函数接受四个参数,第一个参数buffer是C语言中字符数组用于存储日期格式化后的字符串,第二个参数是写入buffer数组的最大值,如果格式化的字符串大于这个值,那么只会取字符串的的一部分,第三个参数"%Y年%m月%d日%H时%M分%S秒"是日期格式,第四个参数localtime(&timeInterval)是指向使用当地时区对时间戳处理得到tm类型结构体的指针
附上tm结构体:
struct tm { int tm_sec; /* seconds after the minute [0-60] */ int tm_min; /* minutes after the hour [0-59] */ int tm_hour; /* hours since midnight [0-23] */ int tm_mday; /* day of the month [1-31] */ int tm_mon; /* months since January [0-11] */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday [0-6] */ int tm_yday; /* days since January 1 [0-365] */ int tm_isdst; /* Daylight Savings Time flag */ long tm_gmtoff; /* offset from UTC in seconds */ char *tm_zone; /* timezone abbreviation */ };
Swift三种情况下的代码
//不进行缓存 func testInOldWay(_ times: Int) { var string = "" var date = Date.init() let startTime = CFAbsoluteTimeGetCurrent(); for i in 0..<times { let formatter = DateFormatter() formatter.dateFormat = "yyyy年MM月dd日HH时mm分ss秒" date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) string = formatter.string(from: date) } let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; print("使用oldWay计算\n\(times)次,总耗时\n\(duration) ms\n") } //进行缓存 func testInNewWay(_ times: Int) { var string = "" var date = Date.init() let startTime = CFAbsoluteTimeGetCurrent(); for i in 0..<times { date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) string = DateFormatterCache.shared.dateFormatterOne.string(from: date) } let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; print("使用缓存Formatter的方案计算\n\(times)次,总耗时\n\(duration) ms\n") } //使用C语言来做日期处理 func testFormatterInC(_ times: Int) { var date = Date.init() var dateString = "" var buffer = [Int8](repeating: 0, count: 100) var time = time_t(date.timeIntervalSince1970) let format = "%Y年%m月%d日%H时%M分%S秒" let startTime = CFAbsoluteTimeGetCurrent(); for i in 0..<times { date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) time = time_t(date.timeIntervalSince1970) strftime(&buffer, buffer.count, format, localtime(&time)) dateString = String.init(cString: buffer, encoding: String.Encoding.utf8) ?? "" } let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; print("使用C语言的方案计算\n\(times)次,总耗时\n\(duration) ms\n") print(dateString) }
iOS 12.1 iPhone 7
测试结果:
测试结果:
在Objective-C中,
不使用缓存,使用缓存,使用C语言函数处理的耗时比约为100:10.7:3.5
在Swift中,
不使用缓存,使用缓存,使用C语言函数处理的耗时比约为100:11.7:6.6
Swift在使用DateFormatter进行处理时,不论是缓存的方案还是不缓存的方案,跟使用Objective-C的耗时基本一致,而在Swift中使用C语言的函数来做日期处理时,时间约为使用Objective-C的两倍,而且当只做一次日期处理时,由于涉及到一些初始资源的初始化,所以看上去比后面执行10次的时间还多
最后
如果项目是Objective-C的项目,我觉得可以采用这种C语言的strftime来做日期处理,能将时间降低为缓存NSDateFormatter的方案的33%左右,如果是Swift项目,调用C语言函数的效率没有在Objective-C项目中那么高,虽然能将时间降低为缓存NSDateFormatter的方案的56%左右,但是在Swift中使用C语言的函数存在一定的风险,在这里风险之一就是time = time_t(date.timeIntervalSince1970)
这行代码返回的值是time_t类型,time_t类型的定义如下:
public typealias time_t = __darwin_time_t public typealias __darwin_time_t = Int /* time() */
time_t其实就是Int,当Swift项目运行在32位设备(也就是iphone 5,iphone 5C)上时,Int类型是32位的,最大值为2147483647,如果这是一个时间戳的值,转换为正常时间是2038-01-19 11:14:07,也就是处理的时间是未来的日期,2038年以后的话,会出现数值溢出。
Demo在这里:
https://github.com/577528249/...
参考资料:
https://forums.developer.appl...
https://stackoverflow.com/que...
PS:
建了一个技术氛围较为浓厚的微信群,欢迎更多对技术有热情的同学加入,可以加我微信,我拉大家进群。