iOS中關(guān)于模塊化開(kāi)發(fā)解決方案(純干貨)
關(guān)于iOS模塊化開(kāi)發(fā)解決方案網(wǎng)上也有一些介紹,但真正落實(shí)在在具體的實(shí)例卻很少看到,計(jì)劃編寫(xiě)系統(tǒng)文章來(lái)介紹關(guān)于我對(duì)模塊化解決方案的理解,里面會(huì)有包含到一些關(guān)于解耦、路由、封裝、私有Pod管理等內(nèi)容;并編寫(xiě)的一個(gè)實(shí)例項(xiàng)目放在git進(jìn)行開(kāi)源[jiaModuleDemo],里面現(xiàn)在已經(jīng)放著一些封裝的功能模塊;會(huì)不斷的進(jìn)行更新,假如你感興趣可以Star一下,項(xiàng)目也不斷的更新完善優(yōu)化;如果你有更好的方案或者說(shuō)好的建議可以lssues,我會(huì)在短時(shí)間進(jìn)行更新并修改相應(yīng)的問(wèn)題;
一:項(xiàng)目中存在的問(wèn)題
1:當(dāng)公司里面有多個(gè)項(xiàng)目同時(shí)進(jìn)行,并且有可能是多個(gè)人分別不同項(xiàng)目時(shí),就會(huì)存在如上圖出現(xiàn)的情況,其實(shí)每個(gè)APP中都是有很多共同的模塊,當(dāng)然有可能你會(huì)把相同功能模塊代碼復(fù)制一份在新項(xiàng)目中,但這其實(shí)并不是最好的方式,在后期不斷迭代過(guò)程中,不同的人會(huì)往里面增加很多帶有個(gè)人色彩的代碼;這樣就像相同的模塊項(xiàng)目后期對(duì)于多個(gè)項(xiàng)目統(tǒng)一管理也是災(zāi)難性,有可能會(huì)失控,哪怕項(xiàng)目轉(zhuǎn)移別人接手也會(huì)無(wú)形中浪費(fèi)很多時(shí)間,增加維護(hù)成本,所以實(shí)例中更注重對(duì)于一些相同模塊進(jìn)行提取,求同存異;而模塊化結(jié)合私有Pods進(jìn)行管理,對(duì)于常用功能的封裝,只要開(kāi)放出一些簡(jiǎn)單開(kāi)關(guān)配置方式,就可以實(shí)現(xiàn)一個(gè)功能,比如日志記錄、網(wǎng)絡(luò)請(qǐng)求模塊、網(wǎng)絡(luò)狀態(tài)變化提示等;
2:對(duì)于頁(yè)面之間相互耦合,而頁(yè)面之間的傳參也各不相同,由于不同的開(kāi)發(fā)人員或者簡(jiǎn)便方式等原因,傳參的類(lèi)型都有差異,包含如實(shí)體、簡(jiǎn)單基本類(lèi)型等,先前項(xiàng)目對(duì)于路由方式也不支持,導(dǎo)致要實(shí)現(xiàn)收到消息推送進(jìn)行不同的頁(yè)面跳轉(zhuǎn)存在硬編碼情況,對(duì)于功能擴(kuò)展存在相當(dāng)大的問(wèn)題;而右邊則是模塊化后頁(yè)面之間的交互方式;頁(yè)面之間也不存在耦合關(guān)系,都只跟JiaMediator這個(gè)中介者相依賴;而傳參都統(tǒng)一成以字典的形式;雖然可能犧牲一些方便跟隨意,卻可以解耦模塊化;并且加入對(duì)路由方式的處理;約定好相關(guān)的協(xié)議進(jìn)行交互;用這種路由方式代替那些第三方的路由插件則是因?yàn)樗撵`活性,最主要還是省去了第三方路由插件在啟動(dòng)時(shí)要注冊(cè)路由的問(wèn)題;
二:解決方案實(shí)現(xiàn)之模塊化
1:JiaCore(基礎(chǔ)功能封裝)
JiaCore是整個(gè)APP最基礎(chǔ)模塊,所有的模塊化都要依賴,主要包含一些全局的功能模塊,比如JiaBaseViewController、JiaAppDelegate等;目前已經(jīng)把一些默認(rèn)的功能進(jìn)行集成在里面,包含網(wǎng)絡(luò)狀態(tài)變化判斷及提示、日志記錄功能等;并把一些相關(guān)配置的內(nèi)容用JiaCoreConfigManager這個(gè)管理類(lèi)進(jìn)行統(tǒng)一設(shè)置,比如是否打開(kāi)日志記錄功能;JiaCoreConfigManager類(lèi)則是開(kāi)放給具體APP設(shè)置全局的相關(guān)配置;下面就以其中一個(gè)日志記錄功能進(jìn)行講解:
//JiaCore基礎(chǔ)模塊相關(guān)配置 JiaCoreConfigManager *jiaCoreConfig=[JiaCoreConfigManager sharedInstance]; jiaCoreConfig.recordlogger=YES; 然后具體APP的PrefixHeader.pch引入命名空間并進(jìn)行設(shè)置記錄日志的等級(jí): #import "JiaCocoaLumberjack.h" //DDLog等級(jí) static const int ddLogLevel = DDLogLevelVerbose;
這樣就完成的一個(gè)APP對(duì)于日志記錄模塊的引入,JiaCore已經(jīng)幫你完成的關(guān)于日志記錄的相關(guān)配置,并且錯(cuò)誤內(nèi)容以一種可讀性較好的格式記錄到file文件中,而且這些file文件生成規(guī)則也都定義好了,當(dāng)然如何時(shí)你要是在Xcode控制臺(tái)顯示不同等級(jí)色彩,只要安裝XcodeColors插件并簡(jiǎn)單進(jìn)行設(shè)置就可以了,對(duì)于不同等級(jí)不同色彩都已經(jīng)在JiaCore配置完成;
2:JSPatch熱更新功能
在JiaCore里面也默認(rèn)集成了熱更新的功能,只要傳入簡(jiǎn)單的對(duì)象數(shù)組就會(huì)啟動(dòng)熱更新;其中JiaPathchModel已經(jīng)是定義好的模型,在APP中把接口請(qǐng)求轉(zhuǎn)化成模型數(shù)組,其中patchId是唯一值名稱、md5則是JS文件的MD5值、url是JS的下載路徑、ver則是對(duì)哪個(gè)版本起作用;因?yàn)橐话阄覀冊(cè)谕饷娴腁PP都是多版本共存,熱更新也要進(jìn)行版本區(qū)分,只下載與本版本相對(duì)應(yīng)的熱更新JS文件加載;而MD5值則是為了增加安全性,避免JS文件被別人進(jìn)行修改而影響APP的運(yùn)行,在JiaCore會(huì)對(duì)下載后的JS文件進(jìn)行MD5計(jì)算并比較;對(duì)于沒(méi)有在jSPatchMutableArray以前的JS文件會(huì)被刪除;
//熱更新內(nèi)容 JiaPathchModel *sample=[[JiaPathchModel alloc]init]; sample.patchId = @"patchId_sample1"; sample.md5 = @"2cf1c6f6c5632dc21224bf42c698706b"; sample.url = @"http://test.qshmall.net:9090/demo1.js"; sample.ver = @"1"; JiaPathchModel *sample1=[[JiaPathchModel alloc]init]; sample1.patchId = @"patchId_sample2"; sample1.md5 = @"e8a4eaeadce5a4598fb9a868e09c75fd"; sample1.url = @"http://test.qshmall.net:9090/demo2.js"; sample1.ver = @"1"; //JiaCore基礎(chǔ)模塊相關(guān)配置 JiaCoreConfigManager *jiaCoreConfig=[JiaCoreConfigManager sharedInstance]; jiaCoreConfig.jSPatchMutableArray=[@[sample,sample1] mutableCopy];
3:JiaGT模塊(個(gè)推封裝)
消息推送對(duì)于一個(gè)APP是相當(dāng)重要性,一般是采用第三方的SDK進(jìn)行集成,其實(shí)大部分的SDK處理代碼都是差不多,在這實(shí)例中對(duì)差異化的內(nèi)容進(jìn)行提取,實(shí)例中將以個(gè)推進(jìn)行模塊化,因?yàn)橄⑼扑偷拇蟛糠执a都集中在AppDelegate中,造成的一大堆雜亂代碼,當(dāng)然也有一部分人對(duì)AppDelegate進(jìn)行擴(kuò)展分類(lèi)進(jìn)行移除代碼,實(shí)例中將采用另外一種解決方案進(jìn)行抽取,可以達(dá)到完全解耦,在具體的APP里面將不會(huì)再出現(xiàn)個(gè)推SDK相關(guān)內(nèi)容,只要簡(jiǎn)單進(jìn)行配置跟處理消息就可以,下面只是簡(jiǎn)單的列出部分代碼,其它封裝代碼見(jiàn)源代碼;
//設(shè)置個(gè)推模塊的配置 jiaGTConfigManager *gtConfig=[jiaGTConfigManager sharedInstance]; gtConfig.jiaGTAppId=@"0uuwznWonIANoK07JeRWgAs"; gtConfig.jiaGTAppKey=@"26LeO4stbrA7TeyMUJdXlx3"; gtConfig.jiaGTAppSecret=@"2282vl0IwZd9KL3ZpDyoUL7"; #pragma mark 消息推送相關(guān)處理 /** * @author wujunyang, 16-07-07 16:07:25 * * @brief 處理個(gè)推消息 * * @param NotificationMessage */ -(void)gtNotification:(NSDictionary *)NotificationMessage { NSLog(@"%@",NotificationMessage[@"payload"]); NSLog(@"-----接收到個(gè)推通知------"); } /** * @author wujunyang, 16-07-07 16:07:40 * * @brief 處理遠(yuǎn)程蘋(píng)果通知 * * @param RemoteNotificationMessage */ -(void)receiveRemoteNotification:(NSDictionary *)RemoteNotificationMessage { NSLog(@"%@",RemoteNotificationMessage[@"message"]); NSLog(@"-----接收到蘋(píng)果通知------"); } /** * @author wujunyang, 16-09-21 14:09:33 * * @brief 獲得注冊(cè)成功時(shí)的deviceToken 可以在里面做一些綁定操作 * * @param deviceToken <#deviceToken description#> */ -(void)receiveDeviceToken:(NSString *)deviceToken { NSLog(@"-----當(dāng)前deviceToken:%@------",deviceToken); }
4:JiaAnalytics模塊(友盟統(tǒng)計(jì)封裝)
JiaAnalytics模塊是在友盟統(tǒng)計(jì)SDK跟Aspect相結(jié)合基礎(chǔ)上完成,對(duì)于頁(yè)面的進(jìn)出統(tǒng)計(jì)采用Aop切面方式進(jìn)行,把原本應(yīng)該在每個(gè)頁(yè)面生命周期的統(tǒng)計(jì)代碼移除,App運(yùn)用只要簡(jiǎn)單配置友盟相對(duì)應(yīng)的信息,也可以設(shè)置要統(tǒng)計(jì)頁(yè)面的過(guò)濾條件,目前已經(jīng)有三種如要統(tǒng)計(jì)的開(kāi)頭頁(yè)面的前綴字符串?dāng)?shù)組、要統(tǒng)計(jì)的頁(yè)面名稱字符串?dāng)?shù)組、不統(tǒng)計(jì)的頁(yè)面名稱字符串?dāng)?shù)組;可以結(jié)合使用,達(dá)到精確統(tǒng)計(jì)頁(yè)面的目的;而且把統(tǒng)計(jì)的代碼放在異步線程進(jìn)行,不會(huì)影響主線程的響應(yīng);
__weak typeof(self) ws = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, BOOL animated){ UIViewController *controller = [info instance]; BOOL filterResult=[ws fileterWithControllerName:NSStringFromClass([controller class])]; if (filterResult) { [ws beginLogPageView:[controller class]]; } } error:NULL]; [UIViewController aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, BOOL animated){ UIViewController *controller = [info instance]; BOOL filterResult=[ws fileterWithControllerName:NSStringFromClass([controller class])]; if (filterResult) { [ws endLogPageView:[controller class]]; } } error:NULL]; });
三:解決方案實(shí)現(xiàn)之頁(yè)面解耦
JiaMediator起到一個(gè)中介的作用,所有的模塊間響應(yīng)交互都是通過(guò)它進(jìn)行,每個(gè)模塊都會(huì)對(duì)它進(jìn)行擴(kuò)展分類(lèi)(例如:JiaMediator+模塊A),分類(lèi)主要是為了用于本地間調(diào)用而又不想用路由的方式,若要用路由的方式則要注意關(guān)于路由約束準(zhǔn)確編寫(xiě),它將會(huì)直接影響到能否正確響應(yīng)到目標(biāo);實(shí)例中也有關(guān)于使用通知的方式進(jìn)行回調(diào)參數(shù)的回傳問(wèn)題;
實(shí)例代碼如下:
NSDictionary *curParams=@{kDesignerModuleActionsDictionaryKeyName:@"wujunyang",kDesignerModuleActionsDictionaryKeyID:@"1001",kDesignerModuleActionsDictionaryKeyImage:@"designerImage"}; switch (indexPath.row) { case 0: { UIViewController *viewController=[[JiaMediator sharedInstance]JiaMediator_Designer_viewControllerForDetail:curParams]; [self presentViewController:viewController animated:YES completion:nil]; break; } case 1: { UIViewController *viewController=[[JiaMediator sharedInstance]JiaMediator_Designer_viewControllerForDetail:curParams]; [self.navigationController pushViewController:viewController animated:YES]; break; } case 2: { NSString *curRoue=@"jiaScheme://Designer/nativeFetchDetailViewController?name=wujunyang&ID=1001&image=designerImage"; UIViewController *viewController=[[JiaMediator sharedInstance]performActionWithUrl:[NSURL URLWithString:curRoue] completion:^(NSDictionary *info) { }]; [self.navigationController pushViewController:viewController animated:YES]; break; } case 3: { NSDictionary *userParaDictionary=@{kUserModuleActionsDictionaryKeyID:@"1"}; UIViewController *viewController=[[JiaMediator sharedInstance] JiaMediator_User_viewControllerForDetail:userParaDictionary]; [self.navigationController pushViewController:viewController animated:YES]; break; } default: break; }
四:模塊化結(jié)合私有Pods方案
實(shí)例中只是把相關(guān)模塊化的提取都在一個(gè)工程進(jìn)行體現(xiàn),最后還是要落實(shí)結(jié)合Pods進(jìn)行管理,把每個(gè)模塊分開(kāi)管理,不同的APP可以簡(jiǎn)單通過(guò)Pods指令就可以達(dá)到引入模塊的效果,對(duì)于一些相同模塊可以在不同的APP重復(fù)引用,減小重復(fù)開(kāi)發(fā)成本;
以上所述是小編給大家介紹的iOS中關(guān)于模塊化開(kāi)發(fā)解決方案(純干貨),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
iOS中json解析出現(xiàn)的null,nil,NSNumber的解決辦法
在iOS開(kāi)發(fā)過(guò)程中經(jīng)常需要與服務(wù)器進(jìn)行數(shù)據(jù)通訊,Json就是一種常用的高效簡(jiǎn)潔的數(shù)據(jù)格式,通過(guò)本文給大家介紹iOS中json解析出現(xiàn)的null,nil,NSNumber的解決辦法,感興趣的朋友參考下2016-01-01iOS藍(lán)牙設(shè)備名稱緩存問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于iOS藍(lán)牙設(shè)備名稱緩存問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09iOS開(kāi)發(fā)中使用UIScrollView實(shí)現(xiàn)無(wú)限循環(huán)的圖片瀏覽器
這篇文章主要介紹了iOS開(kāi)發(fā)中使用UIScrollView實(shí)現(xiàn)無(wú)限循環(huán)的圖片瀏覽器的方法,感興趣的小伙伴們可以參考一下2016-03-03iOS11解決UITableView側(cè)滑刪除無(wú)限拉伸的方法
這篇文章主要給大家介紹了關(guān)于iOS11如何解決UITableView側(cè)滑刪除無(wú)限拉伸的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08iOS開(kāi)發(fā)之運(yùn)動(dòng)事件和遠(yuǎn)程控制
在iOS中事件分為三類(lèi):觸摸事件:通過(guò)觸摸、手勢(shì)進(jìn)行觸發(fā)(例如手指點(diǎn)擊、縮放),運(yùn)動(dòng)事件:通過(guò)加速器進(jìn)行觸發(fā)(例如手機(jī)晃動(dòng)),遠(yuǎn)程控制事件:通過(guò)其他遠(yuǎn)程設(shè)備觸發(fā)(例如耳機(jī)控制按鈕)今天我們來(lái)詳細(xì)探討下運(yùn)動(dòng)事件和遠(yuǎn)程控制2016-04-04Xcode 8打印log日志的問(wèn)題小結(jié)及解決方法
這篇文章主要介紹了Xcode 8打印log日志的問(wèn)題小結(jié)及解決方法的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09