最近在排查fireBase上 線上的崩潰時(shí)敷矫,發(fā)現(xiàn)了一個(gè)很有趣的as肉微?引發(fā)的野指針的問(wèn)題辽慕。
先上代碼遵馆,主要是對(duì)YYMemoryCache 讀寫(xiě)的一個(gè)封裝
internal func set(_ data: SparkChartData) {
let cacheData = Data(
timestamp: Date().timeIntervalSince1970,
data: data
)
self.cache.setObject(cacheData, forKey: NSString(string: "\(data.tickerId)"))
}
internal func get(_ tickerId: Int64) -> SparkChartData? {
guard let cacheData = self.cache.object(forKey: NSString(string: "\(tickerId)")) as? SparkChartDataCache.Data else {
return nil
}
return cacheData.data
}
其中cache是YYMemoryCache鲸郊。我們知道YYMemoryCache的所有方法包括setObject getobject都是用pthread_mutex_lock鎖住線程安全的。所以即使這2個(gè)方法是高頻多線程調(diào)用的我們?cè)趓eview代碼時(shí)也沒(méi)有發(fā)現(xiàn)問(wèn)題货邓。
但是線上的fireBase記錄卻實(shí)實(shí)在在的告訴我們這段代碼是有野指針的秆撮。雖然概率極低。不過(guò)我們?cè)诖蛴〉亩褩P畔⒅邪l(fā)現(xiàn)了蛛絲馬跡
0
libswiftCore.dylib
swift_getObjectType + 48
1
libswiftCore.dylib
_bridgeAnyObjectToAny(_:) + 32
2
**********
<compiler-generated>
SparkChartDataCache.get(_:) + 4307961656
_bridgeAnyObjectToAny 和 swift_getObjectType 是 as? 的內(nèi)部實(shí)現(xiàn)换况。在swift中的類(lèi)型轉(zhuǎn)換和object-C中的類(lèi)型轉(zhuǎn)換實(shí)現(xiàn)是完全不同的职辨。
而
self.cache.object(forKey: NSString(string: "\(tickerId)")) as? SparkChartDataCache.Data
雖然這個(gè)代碼看起來(lái)只有一行。但是實(shí)際執(zhí)行的時(shí)候 其實(shí)會(huì)被分解成2步來(lái)完成戈二。
1.self.cache.object(forKey: NSString(string: "(tickerId)"))
- as ? SparkChartDataCache.Data
步驟1 是在YYMemoryCache 保障的線程安全舒裤。而步驟二其實(shí)已經(jīng)離開(kāi)pthread_mutex_lock的作用范圍。也就是說(shuō)在1觉吭,2步驟之間正好有別的線程執(zhí)行了set方法腾供,而YYMemoryCache的set的實(shí)現(xiàn)我們可以發(fā)現(xiàn),是先delete再set的。從而導(dǎo)致了步驟1得到的指針在步驟2時(shí)變成了一個(gè)野指針伴鳖。雖然概率極低节值,但是高并發(fā)時(shí)還是會(huì)出現(xiàn)的。
最后我們?cè)趃et和set用信號(hào)量做了讀寫(xiě)鎖之后黎侈,崩潰消失察署。雖然我們的鎖其實(shí)會(huì)覆蓋YYMemoryCache內(nèi)部的鎖闷游,但是對(duì)效率影響不大峻汉。