在最近的一次項目模塊化實踐中,我重構(gòu)了Hybrid模塊的API分發(fā)機制。重構(gòu)中使用了NSCache,結(jié)果bug改到懷疑人生。
之前的hybrid模塊,API的實現(xiàn)與js-bridge耦合,業(yè)務使用時需要集中注冊API,由于部分UI接口需要依賴于宿主工程,對于模塊的各業(yè)務擴展性支持不好?;诖耍敬沃貥?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設置了一個原生按鈕并注冊了點擊事件。切換前后臺之后,點擊事件的target被置為了nil,導致無法響應。
代碼大概是這樣的:
@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è)務監(jiān)聽了前后臺事件,并同構(gòu)apimanager的接口移除了api
系統(tǒng)收到了內(nèi)存告警的通知,自動清除了cache
其他webview實例dealloc時clean了當前的api
搜索整個工程,監(jiān)聽前后臺事件的位置并沒有與api-manager交互,切換前后臺時也沒有收到系統(tǒng)內(nèi)存告警。查了業(yè)務,并沒有其他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.
如果其他應用需要內(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è)務環(huán)境。
更多內(nèi)容歡迎關注我的公眾賬號,搜索“ smallyou_chmn” 查看更多精彩內(nèi)容