一個iOS模塊化開發解決方案

jiaModuleDemo項目是爲了解決關於項目中如何進行模塊化開發而編寫的實例,包含如何進行路由式、本地模塊間交互的實現;目前仍是在頁面層級進行抽離,對於項目中各個模塊共有的基礎功能也進行提取,能夠結合私有Pods進行管理;java

項目中存在的問題ios

  • 問題一:頁面耦合嚴重git

18.png

上面這張圖中左邊體現了目前項目中存在的問題,對於頁面之間相互耦合,而頁面之間的傳參也各不相同,因爲不一樣的開發人員或者簡便方式等緣由,傳參的類型都有差別,包含如實體、簡單基本類型等,先前項目對於路由方式也不支持,致使要實現收到消息推送進行不一樣的頁面跳轉存在硬編碼狀況,對於功能擴展存在至關大的問題;而右邊則是模塊化後頁面之間的交互方式;頁面之間也不存在耦合關係,都只跟JiaMediator這個中介者相依賴;而傳參都統一成以字典的形式;雖然可能犧牲一些方便跟隨意,卻能夠解耦模塊化;而且加入對路由方式的處理;約定好相關的協議進行交互;用這種路由方式代替那些第三方的路由插件則是由於它的靈活性,最主要仍是省去了第三方路由插件在啓動時要註冊路由的問題;github

  • 問題二:相同模塊重複開發web

19.png

當公司裏面有多個項目同時進行,而且有多是多我的分別不一樣項目時,就會存在如上圖出現的狀況,其實每一個APP中都是有不少共同的模塊,固然有可能你會把相同功能模塊代碼複製一份在新項目中,但這其實並非最好的方式,在後期不斷迭代過程當中,不一樣的人會往裏面增長不少帶有我的色彩的代碼;這樣就像相同的模塊項目後期對於多個項目統一管理也是災難性,有可能會失控,哪怕項目轉移別人接手也會無形中浪費不少時間,增長維護成本,因此實例中更注重對於一些相同模塊進行提取,求同存異;而模塊化結合私有Pods進行管理,對於經常使用功能的封裝,只要開放出一些簡單開關配置方式,就能夠實現一個功能,好比日誌記錄、網絡請求模塊、網絡狀態變化提示等;數組

模塊化解決方案安全

頁面交互解耦微信

20.png

實現調用代碼:網絡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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:
         {
             UIViewController *viewController=[[JiaMediator sharedInstance]performActionWithUrl:[NSURL URLWithString:curRoue] completion:^(NSDictionary *info) {
             }];
             [self.navigationController pushViewController:viewController animated:YES];
             break ;
         }
         default :
             break ;
     }


上面針對本地模塊調用及路由方式調用的跳轉app

1:JiaMediator起到一箇中介的做用,全部的模塊間響應交互都是經過它進行,每一個模塊都會對它進行擴展分類(例如:JiaMediator+模塊A),分類主要是爲了用於本地間調用而又不想用路由的方式,若要用路由的方式則要注意關於路由約束準確編寫,它將會直接影響到可否正確響應到目標;

3 (1).png

2:JiaMediator是每一個模塊都要用到的內容,能夠把它放在公共的模塊中,由於關於各個模塊的JiaMediator由每一個模塊自個負責,開放給要調用的模塊使用;

3:爲了解耦對於頁面間的傳參都採用字典形式,項目中全部的頁面都繼承於一個基頁面jiaBaseViewController,裏面已經有對初始化對於字典參數的接收並賦值,每一個模塊的子頁面只要調用parameterDictionary屬性,就能夠獲取關於參數的內容;一樣jiaBaseViewController也是每一個模塊都要使用,因此也被提取在公共裏面,其還包括一些導欄條的封裝及關於網絡狀態變化的提示等;

1
2
3
4
5
6
7
8
9
10
11
12
13
//頁面接收參數
@property(nonatomic,strong)NSDictionary *parameterDictionary;
//初始化參數
- (id)initWithRouterParams:(NSDictionary *)params;
 
- (id)initWithRouterParams:(NSDictionary *)params {
     self = [ super  init];
     if  (self) {
         _parameterDictionary=params;
         NSLog(@ "當前參數:%@" ,params);
     }
     return  self;
}

21.png

4:當響應某一個模塊目標後,將會把相應的viewController進行返回,而對於具體如何操做則是在得到當前控制器自行處理,好比是跳轉仍是彈出展示;

5:爲了減小對於字典參數key拼寫錯誤問題,每一個模塊都有一個對應key值的常量配置文件,已經把對應的key值都定義成的常量,方便調用;

1
2
3
4
5
6
7
8
9
10
#ifndef HeaderDesignerConfig_h
#define HeaderDesignerConfig_h
 
//鍵值
static NSString * const kDesignerModuleActionsDictionaryKeyName=@ "name" ;
static NSString * const kDesignerModuleActionsDictionaryKeyID=@ "ID" ;
static NSString * const kDesignerModuleActionsDictionaryKeyImage=@ "image" ;
 
#endif /* HeaderDesignerConfig_h */
  NSDictionary *curParams=@{kDesignerModuleActionsDictionaryKeyName:@ "wujunyang" ,kDesignerModuleActionsDictionaryKeyID:@"1

JiaCore(基礎功能封裝)

JiaCore是整個APP最基礎模塊,全部的模塊化都要依賴,主要包含一些全局的功能模塊,好比JiaBaseViewController、JiaAppDelegate等;目前已經把一些默認的功能進行集成在裏面,包含網絡狀態變化判斷及提示、日誌記錄功能等;並把一些相關配置的內容用JiaCoreConfigManager這個管理類進行統一設置,好比是否打開日誌記錄功能;JiaCoreConfigManager類則是開放給具體APP設置全局的相關配置;下面就以其中一個日誌記錄功能進行講解:

1
2
3
//JiaCore基礎模塊相關配置
JiaCoreConfigManager *jiaCoreConfig=[JiaCoreConfigManager sharedInstance];
jiaCoreConfig.recordlogger=YES;

而後具體APP的PrefixHeader.pch引入命名空間並進行設置記錄日誌的等級:

1
2
3
4
#import "JiaCocoaLumberjack.h"
 
//DDLog等級
static const int ddLogLevel = DDLogLevelVerbose;

這樣就完成的一個APP對於日誌記錄模塊的引入,JiaCore已經幫你完成的關於日誌記錄的相關配置,而且錯誤內容以一種可讀性較好的格式記錄到file文件中,並且這些file文件生成規則也都定義好了,固然如什麼時候你要是在Xcode控制檯顯示不一樣等級色彩,只要安裝XcodeColors插件並簡單進行設置就能夠了,對於不一樣等級不一樣色彩都已經在JiaCore配置完成;

在JiaCore裏面也默認集成了熱更新的功能,只要傳入簡單的對象數組就會啓動熱更新;其中JiaPathchModel已是定義好的模型,在APP中把接口請求轉化成模型數組,其中patchId是惟一值名稱、md5則是JS文件的MD5值、url是JS的下載路徑、ver則是對哪一個版本起做用;由於通常咱們在外面的APP都是多版本共存,熱更新也要進行版本區分,只下載與本版本相對應的熱更新JS文件加載;而MD5值則是爲了增長安全性,避免JS文件被別人進行修改而影響APP的運行,在JiaCore會對下載後的JS文件進行MD5計算並比較;對於沒有在jSPatchMutableArray之前的JS文件會被刪除;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//熱更新內容
JiaPathchModel *sample=[[JiaPathchModel alloc]init];
sample.patchId = @ "patchId_sample1" ;
sample.md5 = @ "2cf1c6f6c5632dc21224bf42c698706b" ;
sample.ver = @ "1" ;
 
JiaPathchModel *sample1=[[JiaPathchModel alloc]init];
sample1.patchId = @ "patchId_sample2" ;
sample1.md5 = @ "e8a4eaeadce5a4598fb9a868e09c75fd" ;
sample1.ver = @ "1" ;
 
//JiaCore基礎模塊相關配置
JiaCoreConfigManager *jiaCoreConfig=[JiaCoreConfigManager sharedInstance];
jiaCoreConfig.jSPatchMutableArray=[@[sample,sample1] mutableCopy];

JiaNetWork(網絡交互封裝)

對於網絡請求模塊則採用YTKNetwork,底層仍是以AFNetworking進行網絡通訊交互,定義一個繼承於YTKBaseRequest的JiaBaseRequest,針對JiaBaseRequest則是爲了後期各個APP能夠對它進行分類擴展,對於一些超時、請求頭部等進行統一個性化設置,畢竟這些是每一個APP都不相同;而針對模塊中關於請求網絡的前綴設置,則在每一個模塊中都有一個單例的配置類,此配置類是爲了針對該模塊對不一樣APP變化而定義;相應的配置內容開放給APP,由具體APP來定義,例如如今項目中的JiaBaseRequest+App.h類,裏面有簡單設置超時跟頭部;固然記得把這個分類引入到APP中,好比AppPrefixHeader這個APP的全局頭部;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "JiaBaseRequest+App.h"
 
@implementation JiaBaseRequest (App)
 
- (NSTimeInterval)requestTimeoutInterval {
     return  15;
}
 
//公共頭部設置
- (NSDictionary *)requestHeaderFieldValueDictionary
{
     NSDictionary *headerDictionary=@{@ "platform" :@ "ios" };
     return  headerDictionary;
}
 
@end

網絡層總體實現以下:

22.png

JiaGT模塊(個推封裝)

消息推送對於一個APP是至關重要性,通常是採用第三方的SDK進行集成,其實大部分的SDK處理代碼都是差很少,在這實例中對差別化的內容進行提取,實例中將以個推動行模塊化,由於消息推送的大部分代碼都集中在AppDelegate中,形成的一大堆雜亂代碼,固然也有一部分人對AppDelegate進行擴展分類進行移除代碼,實例中將採用另一種解決方案進行抽取,能夠達到徹底解耦,在具體的APP裏面將不會再出現個推SDK相關內容,只要簡單進行配置跟處理消息就能夠,下面只是簡單的列出部分代碼,其它封裝代碼見源代碼;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//設置個推模塊的配置
jiaGTConfigManager *gtConfig=[jiaGTConfigManager sharedInstance];
gtConfig.jiaGTAppId=@ "0uuwznWonIANoK07JeRWgAs" ;
gtConfig.jiaGTAppKey=@ "26LeO4stbrA7TeyMUJdXlx3" ;
gtConfig.jiaGTAppSecret=@ "2282vl0IwZd9KL3ZpDyoUL7" ;
 
#pragma mark 消息推送相關處理
 
/**
  *  @author wujunyang, 16-07-07 16:07:25
  *
  *  @brief  處理個推消息
  *
  *  @param NotificationMessage
  */
-(void)gtNotification:(NSDictionary *)NotificationMessage
{
     NSLog(@ "%@" ,NotificationMessage[@ "payload" ]);
     NSLog(@ "-----接收到個推通知------" );
}
 
/**
  *  @author wujunyang, 16-07-07 16:07:40
  *
  *  @brief  處理遠程蘋果通知
  *
  *  @param RemoteNotificationMessage
  */
-(void)receiveRemoteNotification:(NSDictionary *)RemoteNotificationMessage
{
     NSLog(@ "%@" ,RemoteNotificationMessage[@ "message" ]);
     NSLog(@ "-----接收到蘋果通知------" );
}
 
/**
  *  @author wujunyang, 16-09-21 14:09:33
  *
  *  @brief 得到註冊成功時的deviceToken 能夠在裏面作一些綁定操做
  *
  *  @param deviceToken 
  */
-(void)receiveDeviceToken:(NSString *)deviceToken
{
     NSLog(@ "-----當前deviceToken:%@------" ,deviceToken);
}

23.png

上面可以對個推動行徹底的解耦不得不提一個第三方的插件XAspect,若是想對它進行了解能夠在github進行查找;它的主要做用以下圖,能夠用它進行其它第三方SDK的抽離

24.png

JiaAnalytics模塊(友盟統計封裝)

JiaAnalytics模塊是在友盟統計SDK跟Aspect相結合基礎上完成,對於頁面的進出統計採用Aop切面方式進行,把本來應該在每一個頁面生命週期的統計代碼移除,App運用只要簡單配置友盟相對應的信息,也能夠設置要統計頁面的過濾條件,目前已經有三種如要統計的開頭頁面的前綴字符串數組、要統計的頁面名稱字符串數組、不統計的頁面名稱字符串數組;能夠結合使用,達到精確統計頁面的目的;並且把統計的代碼放在異步線程進行,不會影響主線程的響應;

25.jpg

26.png

JiaShare模塊(友盟分享及第三方登陸封裝)

JiaShare模塊運用友盟分享最新版的SDK進行封裝,並把一些其它不是很經常使用的去除,目前只支持新浪、微信聊天、微信朋友圈、QQ聊天頁面、qq空間、騰訊微博;分享包括純文本、圖文、URL、視頻地址、音樂地址,並在裏面已經運用JavaScriptCore.framework封裝好關於H5頁面調用分享並傳參的功能;能夠直接在UIWebView裏面進行調用;本模塊只是對功能進行封裝,對於友盟在info.plist的配置仍是要自行手動,如不明白能夠直接到友盟分享官網查看,項目到也有配置好一份能夠直接參考;JiaPlatformHelper類裏面有一個判斷當前手機是否有安裝相應平臺軟件的方法,能夠用它進行隱藏相應的操做功能,避免上架時審覈不過;下面的代碼是在項目的AppDelegate裏面進行配置.

1
2
3
4
5
6
7
8
//友盟分享
JiaShareConfigManager *jiaShareConfig=[JiaShareConfigManager sharedInstance];
jiaShareConfig.shareAppKey=@ "57e3f1cbe0f55a42080011ec" ;
jiaShareConfig.shareLogEnabled=YES;
//設置平臺
[jiaShareConfig setPlaform:JiaSocialPlatConfigType_Tencent appKey:@ "100424468"  appSecret:@ "c7394704798a158208a74ab60104f0ba"  redirectURL:@ "http://www.umeng.com/social" ];
[jiaShareConfig setPlaform:JiaSocialPlatConfigType_Wechat appKey:@ "wxdc1e388c3822c80b"  appSecret:@ "3baf1193c85774b3fd9d18447d76cab0"  redirectURL:@ "http://www.umeng.com/social" ];
[jiaShareConfig setPlaform:JiaSocialPlatConfigType_Sina appKey:@ "3921700954"  appSecret:@ "04b48b094faeb16683c32669824ebdad"  redirectURL:@ "http://sns.whalecloud.com/sina2/callback" ];

而後就能夠進行分享,在ViewController裏面進行調用,JiaSocialPlatformType是分享平臺的枚舉,shareUrlDataWithPlatform爲URL分享方式,其它能夠直接見源代碼JiaShareHelper類

1
2
3
4
5
6
     [JiaShareHelper shareUrlDataWithPlatform:JiaSocialPlatformType_WechatSession withShareUrl:@ "http://www.sina.com.cn"  withTitle:@ "新浪"  withDescr:@ "新浪網頁"  withThumImage:@ "http://dev.umeng.com/images/tab2_1.png"  withCompletion:^(id result, NSError *error) {
         if (error)
         {
             NSLog(@ "分享出錯了" );
         }
     }];

若是有加載H5頁面,並且也要進行分享的功能就可使用JiaWebShareHelper,由於使用到的是JavaScriptCore,因此只能用在UIWebView中,若是你要是使用WKWebView能夠自個再進行封閉,下面是加載H5頁面中的webViewDidFinishLoad代碼;

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)webViewDidFinishLoad:(UIWebView *)webView {
     self.jsContext = [webView valueForKeyPath:@ "documentView.webView.mainFrame.javaScriptContext" ];
     // 經過模型調用方法,這種方式更好些。
     JiaWebShareHelper *shareHelper  = [[JiaWebShareHelper alloc] init];
     self.jsContext[@ "jia" ] = shareHelper;
     shareHelper.jsContext = self.jsContext;
     shareHelper.webView = self.webView;
     
     self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
         context.exception = exceptionValue;
         NSLog(@ "異常信息:%@" , exceptionValue);
     };
}

H5中就能夠很方便直接進行:

27.png

下面簡單介紹關於第三方登陸跟獲取用戶信息的功能,其功能代碼放在JiaPlatformHelper裏面,已經對受權成功及獲取用戶信息都有回去調相應的參數回來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (![JiaPlatformHelper installPlatAppWithType:JiaSocialPlatformType_QQ])
{
     [weakSelf showResult:@ "沒有安裝QQ軟件,將此功能隱藏" ];
     return ;
}
 
[JiaPlatformHelper authWithPlatform:JiaSocialPlatformType_QQ withCompletion:^(NSString *uid, NSString *openid,NSString *accessToken, NSError *error) {
     if  (error) {
         NSLog(@ "出錯了" );
         return ;
           }
 
     NSString *result=[NSString stringWithFormat:@ "得到到的值爲:uid:%@--token:%@--openid:%@" ,uid,accessToken,openid];
     [weakSelf showResult:result];
}];

獲取用戶信息的代碼以下:

1
2
3
4
5
6
7
8
9
[JiaPlatformHelper getUserInfoWithPlatform:JiaSocialPlatformType_QQ withCompletion:^(NSString *name, NSString *iconUrl, NSString *gender, NSError *error) {
    if  (error) {
       NSLog(@ "出錯了" );
       return ;
     }
     
    NSString *result=[NSString stringWithFormat:@ "得到到的值爲:name:%@--性別:%@" ,name,gender];
    [weakSelf showResult:result];
}];

關於取消受權也有相應的方法:

1
2
3
4
5
6
7
8
9
[JiaPlatformHelper cancelAuthWithPlatform:JiaSocialPlatformType_QQ withCompletion:^(id result, NSError *error) {
    if  (error) {
       NSLog(@ "出錯了" );
       return ;
      }
      
    NSString *ressult=@ "取消成功" ;
    [weakSelf showResult:ressult];
}];

28.png

模塊化結合私有Pods方案

上面實例中只是把相關模塊化的提取都在一個工程進行體現,最後仍是要落實結合Pods進行管理,把每一個模塊分開管理,不一樣的APP能夠簡單經過Pods指令就能夠達到引入模塊的效果,對於一些相同模塊能夠在不一樣的APP重複引用,減少重複開發成本;

29.png

在本項目中已經引入的Pod來管理目前開發的幾個模塊,並導入在我目前的Github的一個庫裏Spec進行統一管理,首先要引入Pod來管理則要增長jiaModule.podspec文件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Pod::Spec. new  do  |s|
 
s.name         =  "jiaModule"
s.version      =  "0.0.6"
s.summary      =  "iOS模塊化功能的引用"
 
s.homepage     =  "https://github.com/wujunyang/jiaModuleDemo"
s.license      = { :type =>  "MIT" , :file =>  "FILE_LICENSE"  }
s.author             = {  "wujunyang"  =>  "wujunyang@126.com"  }
 
s.platform     = :ios,  "7.0"
 
s.source       = { :git =>  "https://github.com/wujunyang/jiaModuleDemo.git" , :tag =>  "0.0.6"  }
 
s.requires_arc =  true
 
s.subspec  'JiaCore'  do  |jiaCore|
jiaCore.source_files =  'jiaModuleDemo/BaseModule/JiaCore/**/*.{h,m}'
jiaCore.dependency  'XAspect'
jiaCore.dependency  'YYCache'
jiaCore.dependency  'JSPatch'
jiaCore.dependency  'RealReachability'
jiaCore.dependency  'FLEX' '~> 2.0'
jiaCore.dependency  'CocoaLumberjack' '~> 2.0.0-rc'
jiaCore.dependency  'AFNetworking' '~>2.6.0'
end
 
s.subspec  'JiaGT'  do  |jiaGT|
jiaGT.source_files =  'jiaModuleDemo/BaseModule/JiaGT/**/*'
jiaGT.dependency  'jiaModule/JiaCore'
jiaGT.dependency  'XAspect'
jiaGT.dependency  'GTSDK' '~> 1.5.0'
end
 
s.subspec  'JiaAnalytics'  do  |jiaAnalytics|
jiaAnalytics.source_files =  'jiaModuleDemo/BaseModule/JiaAnalytics/**/*'
jiaAnalytics.dependency  'jiaModule/JiaCore'
jiaAnalytics.dependency  'XAspect'
jiaAnalytics.dependency  'Aspects'
jiaAnalytics.dependency  'UMengAnalytics-NO-IDFA' '~> 4.1.1'
end
 
s.frameworks =  'UIKit'
 
# s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
# s.dependency "JSONKit", "~> 1.4"
end

上面的文件會把不一樣的模塊進行分離,能夠一塊兒引入也能夠單獨引入某一個模塊;pod會自動把相應的依賴都引入,下面是所有引入關於jiaModule模塊

1
2
3
4
5
platform :ios,  "7.0"
 
pod  'jiaModule'

假如要引入只是其中一個模塊:

1
2
3
4
5
6
platform :ios,  "7.0"
 
pod 'jiaModule/JiaCore’
pod 'jiaModule/JiaGT’

下面簡單介紹兩條關於驗證跟提交jiaModule.podspec的指令,都要打開終端進入項目根目錄,也就是jiaModule.podspec所在的目錄,而後進行執行;

1
2
3
4
5
#驗證是否正確(後面還有一個git的私有地址)
pod lib lint jiaModule.podspec --allow-warnings --use-libraries --sources= 
 
#提交到庫  (specs就是大家的私有庫名,見下面repo add指令時的名字,後面還有一個git的私有地址)
pod repo push specs jiaModule.podspec --allow-warnings --use-libraries --sources=https: //github.com/CocoaPods/Specs.git,https://github.com/wujunyang/WjySpecs.git

注意:若是提交到庫時報下面的問題,說明尚未把私有倉庫集下載到本地:

1
[!] Unable to find the `specs` repo. If it has not yet been cloned, add it via `pod repo add`.

能夠直接執行指令(specs名字能夠自行定義,跟上面提交時對應該上就行):

1
pod repo add specs https: //github.com/wujunyang/WjySpecs.git

會在路徑:/Users/自個電腦用戶名/.cocoapods/repos(被隱藏,要用指令進行顯示出來) 下有一個文件夾specs 另外還有一個是Pod官網的文件夾;

最後還要登陸Git帳號跟密碼,就能夠成功提交了

1
2
3
4
Username  for  'https://github.com' : wujunyang@126.com
Password  for  'https://wujunyang@126.com@github.com'
To https: //github.com/wujunyang/WjySpecs.git
    80ca876..d4f7446  master -> master
相關文章
相關標籤/搜索