Tips之NSCache和Remote module

好久沒有做記錄了,前段時間版本忒忙,最近家人狀況抱恙,只能忙里偷閑記錄兩
個挺有意思的小Tip.包含了解決思路和最終的方案.也方便有遇上同樣問題的同學解決問題.

NSCache的鬼

某一天,在bug系統(tǒng)中看到了一個Ticket,是這樣記錄的:

頁面打開后,會閃爍.

??這是幾個意思?于是按照描述,進行的bug復現(xiàn).明白了閃爍是什么意思.
QA所謂的閃爍,是指的頁面每次進入有圖片的地方,會進行一次閃爍.也就是圖片的刷新.

修復這個bug,可以有2個方法.一是解決"每次"這個問題,使得頁面只有手動刷新的時候才進行圖片的處理.二是找清楚問題的本質(zhì),為什么使用了緩存(sdwebimage),仍然要"閃爍".

對于方法一,當然是不能接受的.一個是業(yè)務需要,另一個是沒有本質(zhì)上解決問題.于是開始查詢緩存的問題.

首先進行一個判定,是否是緩存引起的.于是稍微修改下代碼,把網(wǎng)絡圖片替換成了本地圖片,看看現(xiàn)象是否仍然存在:果然消失了,于是確定是緩存問題.

其次跟蹤代碼,查詢是否是sd緩存被清除:通過斷點調(diào)試,很容易知道,的確是緩存被清空了.從sd的內(nèi)存緩存中拿不到相應的key對應的圖片.

然后查詢代碼,查詢是否有以下2種簡單的可能:

  • 是否調(diào)用了[[SDImageCache sharedImageCache] clearMemory]之類的相關代碼
  • 是否對sd進行了相關設置,比如max size/limit/age等.

然而通過查詢,沒有類似的相關代碼.經(jīng)過一番瞎搞,似乎沒什么轍了.

再想想,復現(xiàn)步驟有一步:按下home鍵再進入.難道和生命周期相關?不過不好排查,原因是:

  • 操作步驟包含多個生命周期,例如enter background,enter forgeground.
  • 除了app delete中的代理以外,還包含了分散在整個項目內(nèi)的通知,并且還有較多的他人模塊.

不過麻煩也得做,從簡到難.首先排除app delegate中是否有影響:果斷注釋掉,不過現(xiàn)象仍然存在.

然后再來排除通知:這個難度就比較大了,注冊的地方太多,再加上幾種通知...

沒辦法了么?想了想,我們?nèi)绻粨Q了通知,攔截我們需要的通知.這樣就不會發(fā)送生命周期相關的通知,注冊的地方就全部失效了.是不是能知道些什么呢?

幾行代碼搞定,攔截了涉及到的幾個生命周期.最后發(fā)現(xiàn)是enter background這個生命周期搞的鬼.

到現(xiàn)在為止,也就是發(fā)現(xiàn)了在sd中,一旦enter background就會清除緩存.于是我們猜測,是sd故意這么寫么?這不科學啊.

追蹤到sd的源碼,內(nèi)存緩存(memory cache),就是一個NSCache.于是給sd的清除內(nèi)存緩存的方法打上斷點,看看在進入后臺時是否會執(zhí)行:然而答案是令人失望的,并沒有.也就是說,sd本身并沒有做這個操作.

這就詭異了,項目中沒有類似的操作,sd沒有,那...難道是系統(tǒng)的?

如果是系統(tǒng)的,那就是系統(tǒng)調(diào)用了這個NSCache的清除方法.因為是全頁面的閃爍,也就是全部緩存都被清除,而不是針對某一個緩存.那么看看NSCache的方法,自然就懷疑到了removeAllObjects這個家伙身上.

于是再次使用run time賦予我們的神器:交換,來驗證我們的想法:NSCache在進入后臺的時候,會自動的刪除相關的value,調(diào)用removeAllObjects這個方法.

Hook了removeAllObjects方法,答案水落石出.

Well done,果然是這樣的.原因找出來了.剩下的事情就簡單了,調(diào)查下具體是怎么一回事.

原來有這么個協(xié)議:NSDiscardableContent.這個協(xié)議一共有4個required的方法:

@protocol NSDiscardableContent
@required
- (BOOL)beginContentAccess;
- (void)endContentAccess;
- (void)discardContentIfPossible;
- (BOOL)isContentDiscarded;
@end

這個協(xié)議決定了存儲在NSCache中的value的一些特性.NSCache會在進入后臺的時候?qū)ζ渲写鎯Φ乃衯alue按照協(xié)議的方式進行保留或者刪除(discard),如果沒有實現(xiàn)該協(xié)議,則默認刪除.

至于這個bug,大不了就是對image實現(xiàn)個category,category實現(xiàn)了該協(xié)議,然后操作代理方法,根據(jù)業(yè)務邏輯來進行判定即可.

Remote Module

組件化是吵了好久的話題了.雖然我愛湊個熱鬧,看看各家的吵吵鬧鬧:道理是越辯越明,從方便項目上升到架構的藝術.不過實在沒有太大興趣,也不敢擅自重復造輪子.但是最近實在是被部分歷史代碼折騰煩了,不得不也開始組件化的道路.

套路還是那個套路,沒有什么新意.基本按照casa的Mediator走.只是有一點思考:

是否要注冊

在casa的Mediator中,認為不需要注冊.因為注冊實際上是一種映射,而有了target-action的話,其實只需要一種轉(zhuǎn)換即可.

然后通過url轉(zhuǎn)換成target-action進行執(zhí)行:

id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];

不過在實際中,因為各種原因,url本身是不會附帶這些信息的.比如url是服務端定的,還要考慮到android等,所以實際情況沒有那么理想.

所以才有注冊一說,才有了是否需要注冊的爭論.

放開這些爭論,這里有個Tip是:即使選擇了注冊方案,也無須手動注冊,自動注冊即可.

因為有objc_getClassList這個東東,小蝦的公眾號專門聊過.

假設在你的Router中,需要用到注冊這貨(不用就算了).這或許是一個方案:

  1. 有一組或者一個.h文件專門負責記錄有哪些URL.這一步從程序上來講,是沒用的,但是從維護的角度來講,是有意義的:
static NSString *const SchemeShooseVideo = @"jumeimall://page/choosevideo";

2.有一個protocol,來約定服務提供方提供的服務(UPDATE:現(xiàn)在取消了這個protocol,否則服務方會依賴這個protocol.目前是直接約定supportedSchemes方法,為了防止命名沖突,方法名可以增加前綴):

@protocol JMMallProtocol <NSObject>

@required

+ (NSArray <NSString *> *)supportedSchemes;

@end

因為服務方可能提供多個URL遠程服務,所以是個NSArray;因為我們使用target-action的方案,所以NSArray中的是字符串,其中包含了target-action信息,也包含了其他的信息(比如url).

3.因為該服務最終由Mediator解析成target-action并且執(zhí)行,所以字符串必須按照規(guī)定的方式進行組裝.所以最好提供一個helper方法.

+ (NSString *)urlFromScheme:(NSString *)url target:(NSString *)target isClass:(BOOL)isClass action:(NSString *)action {
    return [NSString stringWithFormat:@"%@^%@^%@^%@",url,target,(isClass? @"C" : @"O"),action];
}

4.最后使用objc_getClassList這貨進行一個處理,當然注意下細節(jié)即可.

+ (void)load {
    int classCount;
    Class *classes;
    classCount = objc_getClassList(NULL, 0);
    if (classCount <= 0) {
        return;
    }

    NSMutableDictionary *moduls = @{}.mutableCopy;
    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * classCount);
    classCount = objc_getClassList(classes, classCount);

    for (int i = 0; i < classCount; i++) {
        Class c = classes[i];
        const char *name = class_getName(c);
        if (strncmp(name, "JM", 2) != 0 && strncmp(name, "SC", 2) != 0) {
            continue;
        }
        SEL selector = NSSelectorFromString(@"supportedSchemes");
        if (![c respondsToSelector:selector]) {
            continue;
        }
        Method method = class_getClassMethod(c, selector);
        if (strncmp(method_getDescription(method)->types, "@", 1) != 0) {
            continue;
        }
        IMP imp = method_getImplementation(method);
        NSArray <NSString *> *result = (NSArray <NSString *> *)imp(c,selector);
        for (NSString *url in result) {
            NSRange range = [url rangeOfString:@"^"];
            if (range.location == NSNotFound) {
                continue;
            }
            moduls[[url substringToIndex:range.location]] = url;
        }
    }
    [[JMMediator sharedInstance] setValue:moduls forKey:@"modules"];
    free(classes);
}

主要是處理字符串,所以char *NSString之間的轉(zhuǎn)換是挺耗時的操作,所以能用c方法的盡量用.我這里大概2w多個文件處理完畢,耗時0.1s.如果有需求,當然可以做進一步優(yōu)化:)

通過這樣的處理,就可以有以下效果:

  • 有一個/多個列表(.h文件),可以知道需要處理那些url
  • 在內(nèi)存中維護了一個字典,key為url,value為我們組合的信息(target-action)
  • 通過Mediator,在處理remote model的時候,可以通過url查詢字典,拿到對應的字符串,解析后進行target-action的方式進行執(zhí)行.

當然缺點就是..調(diào)用反轉(zhuǎn)了:
應該由調(diào)用方?jīng)Q定哪個url對應執(zhí)行哪個服務,而非服務方將服務進行綁定.

不過一方面是這是個架構藝術的問題,另一方面這也是個tip,如果你要這么做,可以幫著省點事.不這么做,完全可以有其他做法.Up to you!

當然不敢獻丑,具體代碼就不好意思拿出來了.Just a tip!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容

  • iOS應用架構談 組件化方案 討論論壇 源 簡述 前幾天的一個晚上在infoQ的微信群里,來自蘑菇街的Limboy...
    其實也沒有閱讀 1,443評論 1 9
  • 前言: 本文轉(zhuǎn)自前同事casa的博文,這篇文章是基于runtime實現(xiàn)的iOS組件化方案,其實iOS組件化方案基本...
    monkey01閱讀 1,675評論 1 2
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,832評論 18 139
  • 原文鏈接: http://casatwy.com/iOS-Modulization.html?hmsr=touti...
    anddygon閱讀 2,000評論 1 13
  • 技術無極限,從菜鳥開始,從源碼開始。 由于公司目前項目還是用OC寫的項目,沒有升級swift 所以暫時SDWebI...
    充滿活力的早晨閱讀 12,679評論 0 2