在最近的一次項目模塊化實踐中稠炬,我重構(gòu)了Hybrid模塊的API分發(fā)機制。重構(gòu)中使用了NSCache咪啡,結(jié)果bug改到懷疑人生首启。
之前的hybrid模塊,API的實現(xiàn)與js-bridge耦合撤摸,業(yè)務(wù)使用時需要集中注冊API毅桃,由于部分UI接口需要依賴于宿主工程,對于模塊的各業(yè)務(wù)擴展性支持不好准夷≡糠桑基于此,本次重構(gòu)主要是針對hybrid的api分發(fā)機制衫嵌。
核心思想是读宙,通過api-manager來對api進行統(tǒng)一管理與分發(fā),全局維護一組api實例楔绞,api遵循api-protocol協(xié)議并在load中異步進行注冊结闸,webview根據(jù)group來標識一組api。
基于此酒朵,api-manger需要通過緩存來保存當前的api實例桦锄,確保app中所有的webview都可以使用,由于注冊可以使用異步注冊蔫耽,避免出現(xiàn)多線程問題结耀,這個地方的緩存采用了NSCache。
先還原一下現(xiàn)場:ApiManager是一個單例针肥,strong強引用了一個NSCache的實例,通過提供的宏異步注冊了api饼记,代碼大概長這樣:
@interface ApiManager ()
@property (nonatomic, strong) NSCache *cache;
@end
?
@implementation ApiManager
static id _instance;
+ (instancetype)manager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
?
- (void)addObject:(id)object forKey:(NSString *)key {
[self.cache setObject:object forKey:key];
}
?
- (id)objectForkey:(NSString *)key {
return [self.cache objectForKey:key];
}
?
- (NSCache *)cache {
if (_cache == nil) {
_cache = [[NSCache alloc] init];
}
return _cache;
}
@end
當興高采烈的提測之后,噩夢開始了慰枕!提審前一天測試報了bug具则,APP打開了webview,進入后臺之后再次進入前臺,界面上的按鈕失效了具帮。
經(jīng)過確認博肋,cache緩存的ui-api注冊之后低斋,js端通過js-bridge給webview設(shè)置了一個原生按鈕并注冊了點擊事件。切換前后臺之后匪凡,點擊事件的target被置為了nil,導(dǎo)致無法響應(yīng)膊畴。
代碼大概是這樣的:
@interface UserInterfaceApi : NSObject
- (id)setNavigationBar:(id)parameter callback:(block)callback {
...
if (leftItem != nil) {
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:image
style:UIBarButtonItemStylePlain
target:self
action:@selector(actionLeft:)];
}
...
}
- (void)actionLeft:(id)sender {
...
}
@end
唯一的原因是ui-api被釋放了,但在哪里釋放的呢病游?可能的原因有:
其他業(yè)務(wù)監(jiān)聽了前后臺事件唇跨,并同構(gòu)apimanager的接口移除了api
系統(tǒng)收到了內(nèi)存告警的通知,自動清除了cache
其他webview實例dealloc時clean了當前的api
搜索整個工程衬衬,監(jiān)聽前后臺事件的位置并沒有與api-manager交互买猖,切換前后臺時也沒有收到系統(tǒng)內(nèi)存告警。查了業(yè)務(wù)滋尉,并沒有其他webview主動釋放玉控,那問題出在哪里呢?
排除了上述三個原因之后狮惜,只能從cache這里入手了高诺。整個app運行期間,api僅僅被cache強引用碾篡,webview通過groupId來與api綁定時也是使用的弱引用虱而,所以只可能是NSCache出現(xiàn)了問題。
查閱官方文檔开泽,重新閱讀了一下這段話
The NSCache class incorporates various auto-eviction policies, which ensure that a cache doesn’t use too much of the system’s memory. If memory is needed by other applications, these policies remove some items from the cache, minimizing its memory footprint.
如果其他應(yīng)用需要內(nèi)存時薛窥,系統(tǒng)會將NSCache移除,也就是說系統(tǒng)會自己判斷在某個合適的時機清空cache眼姐。之前總以為系統(tǒng)內(nèi)存告警時會被移除,難道合適的時機還包括前后臺切換佩番?
于是众旗,將NSCahe換成了NSMutableDictionary試了一次,發(fā)現(xiàn)bug消失了趟畏」逼纾看來前后臺切換,系統(tǒng)確實會清理掉NSCache赋秀。
問題解決了利朵,那我們來重新總結(jié)一下NSCache。繼續(xù)翻看蘋果開發(fā)者文檔猎莲,我們發(fā)現(xiàn)NSCache有以下特點:
系統(tǒng)會在合適的時間清理掉NSCache
NSCache是線程安全的
key對象不會被拷貝绍弟,也即key無需遵循NSCopying協(xié)議
最后,NSCache使用時需要慎重考慮當前的業(yè)務(wù)環(huán)境著洼。
更多內(nèi)容歡迎關(guān)注我的公眾賬號樟遣,搜索“ smallyou_chmn” 查看更多精彩內(nèi)容