POCT日历页面初始化的阶段在iPhone6上,会有明显的卡顿情况,通过Instrument查看,发现耗时最长的是第三方控件FSCalendar,因为FSCalendar里面的内容未开放源码处理,所以暂时无法对其进行修改。
在查看了代码后,发现日历页面有大量关于时间格式转换相关的内容,而使用DateFormatter会消耗大量的CPU,使用缓存的方式将DateFormatter对象进行缓存会在一定程度上提高效率。下面通过使用缓存和未使用缓存,通过CPU的消耗进行对比,更直观的了解缓存DateFormatter对象可以提高CPU工作效率,减少时间。未使用缓存相关的代码
// 未使用缓存
func yyyyMMddDate() -> Date?{
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
print("yyyy-MM-dd")
return formatter.date(from: self) }
// 使用缓存
func yyyyMMddDateTest() -> Date?{
let thread = Thread.current.threadDictionary
if thread.object(forKey: "cacheFormatterData") != nil {
let f: DateFormatter = thread.object(forKey: "cacheFormatterData") as! DateFormatter
return f.date(from: self)
} else {
let f = DateFormatter()
f.dateFormat = "yyyy/MM/dd"
thread.setObject(f, forKey: "cacheFormatterData" as NSCopying) return f.date(from: self)
}
}
通过这两个方法同样点击10次得到的CPU消耗情况如下图所示:
左边10个表示未缓存时的CPU消耗,右边是缓存了DataFormatter对象所引起的CPU消耗,很明显做了缓存的比未做缓存减少了大量的CPU消耗。所以缓存DataFormatter对象这个方案可以用到日历页面中。接下来我们可能会有新的疑问:因为什么原因使缓存DateFormatter对象CPU的消耗明显减少?
- 创建DateFormatter对象?
- 使用DateFormatter对象的属性或方法?为了验证上面提出的问题,修改了代码,重新进行测试:
func yyyyMMddDate() -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter.date(from: self)
}
// 使用情况 func yyyyMMddDateTest() -> Date? {
let h = DateFormatter()
return Date()
}
同样重复之前的验证操作,得到的结果如下图所示:
看样子创建DataFormatter对象并不是引起CPU大量消耗的原因,是不是意味着,如果我们在缓存的对象中使用了对象的属性或者方法,所产生的CPU消耗和未缓存的应该是一致的。
带着这样一个疑问,我们再对代码进行修改:
```func yyyyMMddDate() -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter.date(from: self)
}
// 使用情况
func yyyyMMddDateTest() -> Date? {
let thread = Thread.current.threadDictionary
if thread.object(forKey: "cacheFormatterData") != nil {
let f: DateFormatter = thread.object(forKey: "cacheFormatterData") as! DateFormatter
f.dateFormat = "yyyy/MM/dd" return f.date(from: self)
} else {
let f = DateFormatter()
f.dateFormat = "yyyy/MM/dd"
thread.setObject(f, forKey: "cacheFormatterData" as NSCopying)
return f.date(from: self)
}
}
重复之前的验证操作,得到的结果如下图所示:
通过这个对比,感觉非常奇怪,明明做了相同的操作,但是使用了缓存的还是明显的比未使用缓存的CPU的消耗更小,这到底是为什么?出于对这个问题存在极大困惑,仔细看了看跟threadDictionary相关的内容,争取从这些内容里面找出使什么操作减少了CPU消耗。
>苹果的官方文档对threadDictionary的描述
You can use the returned dictionary to store thread-specific data. The thread dictionary is not used during any manipulations of the NSThread object—it is simply a place where you can store any interesting data. For example, Foundation uses it to store the thread’s default NSConnection and NSAssertionHandler instances. You may define your own keys for the dictionary.
主要的意思就是threadDictionary只是可以存储任何数据的地方。
看来threadDictionary的这个猜想也不成立。
再回过来看dateFormat的定义
>@property (null_resettable, copy) NSString *dateFormat;
因为调用DateFormat对象的dateFormat属性,而这个属性会进行copy操作,而估计缓存在thread中的DateFormat对象已经进行了copy,所以不用在设置dateFormat属性时进行copy的操作。从而减少了CPU的消耗。
那么是否所有的缓存都会减少消耗呢?
我们使用POCT的NEUserDefault进行相同的缓存操作,具体的代码实现如下图所示:
func yyyyMMddDate() -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter.date(from: self)
}
// 使用情况 func yyyyMMddDateTest() -> Date? {
if NEUserDefaults.init().object(forKey: "cacheFormatterData") != nil {
let f: DateFormatter = NEUserDefaults.init().object(forKey: "cacheFormatterData") as! DateFormatter
return f.date(from: self)
} else {
let f = DateFormatter()
f.dateFormat = "yyyy/MM/dd"
NEUserDefaults.init().setObject(f, forKey: "cacheFormatterData")
return f.date(from: self)
}
}
根据同样的操作我们得到以下的结果:
看到这个图的时候,是比较诧异的,做了缓存和没做缓存的,居然做了缓存的消耗的CPU更高。而这个更加明确了使用
thread.current.threadDictionary进行DataFormatter对象的缓存具有优势。
以后会探索thread.current.threadDictionary的存储内容的原理对比使用NeUserDefault使用缓存的优缺点。
tips:在上面的测试实践中,发现print方法也会明显的消耗CPU的使用。在开发过程中,发布版本应该尽量避免print的使用。