原文鏈接:http://www.itdecent.cn/p/5a45fe49f8b6
相信做App開發(fā)的同學(xué),對于一些第三方的統(tǒng)計分析、錯誤收集等SDK應(yīng)該都不陌生。就目前而言市面上也有許多相同功能的產(chǎn)品,眼花繚亂,讓人無法抉擇選哪一款SDK才是最靠譜的。那就隨便先選一款試試用吧!
那么問題來了:如果項目都快做完了結(jié)果發(fā)現(xiàn)這款SDK實在坑爹,不僅擴展性差,還經(jīng)常讓App Crash,那你是不是會想到替換掉這個SDK?
OK,那我們就換另一個試試,下載SDK下來,一看,傻眼了,設(shè)計風(fēng)格,封裝模塊完全不一樣,于是乎我們就到項目中全局搜索找到之前的SDK代碼干掉,然后重新再到各種地方用新的SDK來寫新的邏輯來替換,關(guān)鍵的是,中間還不知道會產(chǎn)生多少bug,漏掉多少未修改的代碼,總之始終會有一種不靠譜的感覺。
換一次還算好的,如果之后團隊壯大了,這些數(shù)據(jù)分析之類的東西突然想自己做了,畢竟這些有價值的數(shù)據(jù)并不想這么拱手讓給一個第三方的公司嘛~這個時候你是不是就只想說:『呵呵』
所以這個時候適配器模式就起到作用了~
何為適配器模式
GoF對于適配器模式的解釋如下:
將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以在一起工作。
個人通俗理解:
適配器:顧名思義,將不兼容的轉(zhuǎn)換為兼容,如電源適配器,將全世界各種不相同的電壓轉(zhuǎn)換成相同的電壓輸出給目標(biāo)設(shè)備。
這里可以將目標(biāo)設(shè)備理解為『接口』,世界各種電壓可以理解為『產(chǎn)生相同功能的類』,電源適配器可以理解為『需要實現(xiàn)的適配器類』。
適配器模式產(chǎn)生的效果是:在不修改代碼或者修改極少代碼的情況下,快速的切換源(數(shù)據(jù)源、內(nèi)容源等)。
就像電源適配器一樣,去到不同國家,同一個設(shè)備只需要不同的電源適配器就可以使用當(dāng)前國家的電源,而不需要取拆卸機器。
使用真實場景
如文章開頭所講,被某盟的SDK坑了之后(確實在某些狀況下讓App Crash,產(chǎn)生原因初步判斷是濫用performSelector,不考慮對象被釋放的情況而產(chǎn)生的Crash),產(chǎn)生替換念想而思考,如果將來替換豈不是又要苦逼我們自己?
于是乎為了將來的輕松就必須動動腦子去設(shè)計代碼了,于是有了今天的適配器模式實戰(zhàn)。
懶的程序員才是好程序員,所以才會有所謂的『面向?qū)ο蟆?,所謂的『設(shè)計模式』。
代碼
首先我們先定義好目標(biāo)接口,在iOS也就是Protocol
針對統(tǒng)計分析這一個大模塊,我先設(shè)計了KTRUserAnalysisProtocol、KTRUserEvent、KTRAutoUpdate等幾個協(xié)議,通過這些協(xié)議我們可以實現(xiàn)我們想要的功能。
如KTRAutoUpdate中,定義的方法如下:
/**
*? 檢查更新
*
*? @param ower? ? 擁有者,用于判斷擁有者是否存在
*? @param finished 檢查更新完成后回調(diào)函數(shù)
*? @param result? 是否為自動檢查(若為自動檢查則需要檢查網(wǎng)絡(luò)環(huán)境)
*? @param isSilent 是否靜默方式檢查更新
*/- (void)checkUpdateWith:(id)ower isAuto:(BOOL)result isSilent:(BOOL)isSilent callBack:(KTRUserAnalysisVoidBlock)finished;- (void)checkUpdateWith:(id)ower isAuto:(BOOL)result callBack:(KTRUserAnalysisVoidBlock)finished;- (void)checkUpdateWith:(id)ower callBack:(KTRUserAnalysisVoidBlock)finished;/**
*? 判斷是否有更新
*
*? @return 當(dāng)checkUpdateWith檢查完畢之后,一般情況會保存結(jié)果到UserDefault中,在后續(xù)其他UI中可以直接使用而不必網(wǎng)絡(luò)檢查
*/- (BOOL)appIsNeedUpdate;
通過這些方法我就可以進行各種類型的更新檢測了。
定義好Protocol之后需要做的就是創(chuàng)建目標(biāo)基類(抽象類),猶豫Objective-C沒有抽象類的概念所以,只能自己使用特殊的方法來代替,那就是在方法體中使用:
- (void)checkUpdateWith:(id)ower isAuto:(BOOL)result isSilent:(BOOL)isSilent callBack:(KTRUserAnalysisVoidBlock)finished{NSAssert(NO,@"具體適配器必須實現(xiàn)該方法");}
這樣子類在沒有重載方法而調(diào)用該方法的時候就會報運行時錯誤,從而強制其實現(xiàn)。
緊接著就是實現(xiàn)具體的Adapter了,在這個項目中我命名為KTRUMAdapter
實現(xiàn)過程沒有什么太多需要講的,因為各自的業(yè)務(wù)不一樣,實現(xiàn)也就不一樣沒有什么可參考性,但是這里,有一點是比較好玩的,也就之解決前說的UM的SDK沒有考慮對象釋放而產(chǎn)生的Crash。
- (void)checkUpdateWith:(id)ower isAuto:(BOOL)result isSilent:(BOOL)isSilent callBack:(KTRUserAnalysisVoidBlock)finished{self.updateOwer= ower;self.updateCallback= finished;//判斷是否wifi環(huán)境if([selfnetworkStatus] == ReachableViaWiFi || !result)? ? {//如果為靜默方式檢查,則調(diào)用appSilentUpdateif(isSilent) {? ? ? ? ? ? [MobClick cancelPreviousPerformRequestsWithTarget:selfselector:@selector(appSilentUpdate:) object:nil];? ? ? ? ? ? [MobClick checkUpdateWithDelegate:selfselector:@selector(appSilentUpdate:)];return;? ? ? ? }self.checkUpdateTips=YES;? ? ? ? __weaktypeof(self)weakSelf =self;//如果友盟檢測更新10秒后無響應(yīng)則自動回調(diào)dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{if(weakSelf.checkUpdateTips)? ? ? ? ? ? {//取消請求[MobClick cancelPreviousPerformRequestsWithTarget:selfselector:@selector(appUpdate:) object:nil];if(finished)? ? ? ? ? ? ? ? {? ? ? ? ? ? ? ? ? ? finished();? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? weakSelf.checkUpdateTips=NO;? ? ? ? ? ? }? ? ? ? });? ? ? ? [MobClick checkUpdateWithDelegate:selfselector:@selector(appUpdate:)];? ? }else{self.updateCallback= finished;? ? }}/**
*? 友盟檢查更新回調(diào)函數(shù)
*
*? @param appInfo
*/- (void)appUpdate:(NSDictionary*)appInfo{self.checkUpdateTips=NO;if(self.updateOwer)? ? {? ? ? ? [selfalertUpdateView:appInfo];if(self.updateCallback)? ? ? ? {self.updateCallback();? ? ? ? }? ? }}
在設(shè)計的時候我使用單例模式來實現(xiàn)的Adapter,為的就是在App運行過程中不被釋放,這樣就可以保證UM的回調(diào)不會因為找不到對象而回調(diào)方法Crash,然后在單例里面判斷調(diào)用該方法的對象是否還存在,存在則繼續(xù)回調(diào),如果不存在則略過當(dāng)前方法。
在iOS中其實是沒有必要創(chuàng)建這個抽象類的,直接使用Protocol然后用delegate的方式也就能夠完成基類的工作。
但是為了更加方便給客戶端使用,抽象類是一個不錯的選擇,因為在客戶端只需要如此使用:
KTRUserAnalysis *userAnalysis = [KTRUserAnalysis sharedInstanceWithAdapter:[[KTRUMAdapter alloc]init]];BOOLresult = [[KTRUserAnalysis sharedInstance] appIsNeedUpdate];
后續(xù)如果要換掉Adapter,就僅僅改掉此處就皆大歡喜了。當(dāng)然前提是,先要實現(xiàn)另一個Adapter。
總結(jié)
適配器模式的有點很多,如:將客戶端與適配者解耦、使客戶端調(diào)用更加簡明(只需要調(diào)用定義的接口方法)。但同樣也有缺點,在寫適配器的時候為適應(yīng)目標(biāo)接口,可能會有比較復(fù)雜實現(xiàn)過程;無端端多出許多的類,使結(jié)構(gòu)又復(fù)雜了一些。
所以在使用的時候需要因情況而定,不要生搬硬套盲目的使用設(shè)計模式。
文/KevinLee(簡書作者)
原文鏈接:http://www.itdecent.cn/p/5a45fe49f8b6
著作權(quán)歸作者所有,轉(zhuǎn)載請聯(lián)繫作者獲得授權(quán),並標(biāo)註“簡書作者”。