完了!用完NSCache懷疑人生了....

在最近的一次項目模塊化實踐中,我重構(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)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容