[性能优化]DateFormatter深度优化探索

news/2024/7/5 20:21:10 标签: c/c++, swift, 移动开发

前言

在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:
建了一个技术氛围较为浓厚的微信群,欢迎更多对技术有热情的同学加入,可以加我微信,我拉大家进群。


http://www.niftyadmin.cn/n/1148129.html

相关文章

渗透测试业务逻辑之业务接口调用

0x00&#xff1a;前言 上周做渗透&#xff0c;有一个 sql 注入&#xff0c;负责安全审核的人给开发说你们的程序既然还有 sql 注入&#xff0c;我一年也看不见几个。这句话让我又再次深刻的认识到&#xff0c;渗透测试常规的一些注入跨站漏洞不如以前那么盛了&#xff0c;有点…

摩托罗拉份额下滑 手机业老三成最大争夺目标

CNET科技资讯网3月25日国际报道 四面受敌的摩托罗拉这几个月已经失去或者被挖去了一批高管&#xff0c;包括首席财务官、营销与技术官以及移动设备部门总裁。 摩托罗拉位于英国伯明翰的设计中心可能是下一波裁员的所在地&#xff0c;预计将有一半的员工离职。 投资家Carl Ic…

C#中控制线程池的执行顺序

在使用线程池时&#xff0c;当用线程池执行多个任务时&#xff0c;由于执行的任务时间过长&#xff0c;会导制两个任务互相执行&#xff0c;如果两个任务具有一定的操作顺序&#xff0c;可能会导制不同的操作结果&#xff0c;这时&#xff0c;就要将线程池按顺序操作。下面先给…

burpsuite 集成 sqlmap 插件

0x00&#xff1a;前言 之前测试 sql 注入的时候很多平台中的很多查询功能都类似&#xff0c;于是通常就只测第一个功能&#xff0c;如果有问题&#xff0c;报告中就加一句类似此功能请逐一检查并修复。如果没问题&#xff0c;就直接过了。 那么这样测合适么&#xff0c;答案是…

索尼爱立信市场营销总监离职 接任人选未公布

3月26日消息&#xff0c;索尼爱立信日前宣布公司副总裁兼市场营销总监迪伊•杜塔&#xff08;Dee Dutta&#xff09;已经决定在本月底之前离开公司。 据国外媒体报道&#xff0c;迪伊于2002年加入了索尼爱立信公司&#xff0c;自2003年起&#xff0c;他就负责主管市场营销业务…

微软Azure容器服务要关停,你想好怎么迁移了吗?

最近&#xff0c;微软宣布将于2020年1月正式暂停其ACS&#xff08;Azure Container Service&#xff09;服务&#xff0c;并鼓励ACS用户将其分布式基础架构转移到新推出的Azure Kubernetes Service服务上&#xff0c;这对微软来说是一个合乎逻辑的举动。虽然ACS将继续支持Docke…

PHP 代码审计之 SQL 注入

0x00&#xff1a;前言 用 CMS 做例子&#xff0c;今天刚下载安装&#xff0c;因为首页的搜索功能比较显眼&#xff0c;所以看了一下是存在 sql 注入的&#xff0c;基本没有防护机制&#xff0c;CMS 比较冷门&#xff0c;不属于热门&#xff0c;所以安全性也低一点&#xff0c;…

昔日国产手机巨头科健售予房产大佬同方集团

新浪科技讯 3月25日晚间&#xff0c;停牌数月的昔日国产手机巨头中科健(000035)发布公告&#xff0c;称与同方联合控股(以下称同方集团)达成重组协议&#xff0c;将出售给同方集团。这样&#xff0c;历时多年的科健重组终于快画上一个句号&#xff0c;避免了退市。 达成重组协…