# MVC
?框架的所有代碼結(jié)構(gòu)整合都是采用MVC的基礎(chǔ)架構(gòu),這也是蘋果iOS系統(tǒng)的基本架構(gòu)。Controller作為關(guān)鍵角色負(fù)責(zé)View層和Data層的數(shù)據(jù)流交換,現(xiàn)在市面上流行很多架構(gòu)優(yōu)劣的爭論(例如討論比較多的MVVM),但是在我看來,**不論是那種架構(gòu)都是以MVC為基礎(chǔ)的,然后不斷的劃分集體職責(zé),細(xì)化得來的。所以能夠清楚的掌握職責(zé)劃分,將視圖、邏輯和數(shù)據(jù)三者聯(lián)系起來,易用并方便維護(hù),那就可以了,無所謂框架的優(yōu)劣**。
#代碼庫管理
?在本框架的代碼管理中使用的Cocoapod作為第三方依賴的統(tǒng)一管理模塊。
在iOS的開發(fā)過程中會經(jīng)常使用到第三方開源庫,或者跟其他團(tuán)隊合作的時候有些公告的代碼模塊需要很幾個團(tuán)隊使用的情況,更復(fù)雜的情況是某些第三方庫還會再引用其他的第三方庫,所以手動去一個個的下載并配置每個第三庫的使用是一件十分費(fèi)力不討好的事情,而且還可能會出現(xiàn)各種的編譯錯誤。另外如果項(xiàng)目中的類庫需要更新,就需要手動把就的文件刪除在重新下載新版本的類庫然后再添加到工程中去,以上兩種情況是類庫管理中經(jīng)常出現(xiàn)的問題,因此我們引入管理工具Cocoapod來簡明清楚的管理框架中使用的第三方類庫和自定義類庫
CocoaPod資源[https://github.com/CocoaPods/CocoaPods](https://github.com/CocoaPods/CocoaPods)
#網(wǎng)絡(luò)核心模塊
?在本框架中網(wǎng)絡(luò)核心模塊對`AFNetworking`**進(jìn)行了抽象的封裝,抽取**`AFNetworking`的常用的POST和GET請求,并在此基礎(chǔ)上添加網(wǎng)絡(luò)機(jī)密功能。
主要包塊以下幾個主要的功能:
1、表單數(shù)據(jù)上傳功能,主要用于上傳富文本文件
```objective-c
/**
*上傳
*
*@param datas可以同時上傳多個,datas為NSDictionary的集合,NSDictionary中必需包含data, name, fileName, mimeType
*/
-(void)requestWithMultiDatas:(NSArray*)datas params:(NSDictionary*)params;
```
2、非加密POST請求
```objective-c
/*請求非加密-參數(shù)類型只能是字符串或者數(shù)據(jù)字典*/
-(void)requestWithParams:(id)templates;
```
3、加密POST請求,報文體采用AES加密的方式,密鑰由服務(wù)端提供
```objective-c
/*請求加密*/
-(void)requestWithAESEncrpytParams:(NSDictionary*)params token:(NSString*)token;
```
4、Get請求(Get請求均不加密)
```objective-c
/*請求加密*/
-(void)requestWithAESEncrpytParams:(NSDictionary*)params token:(NSString*)token;
```
?網(wǎng)絡(luò)請求模塊作為BaseViewController的基礎(chǔ)功能,因此在實(shí)際使用中由Controller來處理HTTP請求,同時對數(shù)據(jù)進(jìn)行處理和封裝,然后展示給View層。
#資源加載
?資源加載在這里特指App讀取和加載資源圖片,在實(shí)際開發(fā)中需要大量使用圖片來顯示App的視覺效果,但是根據(jù)硬件的物理尺寸不同,在讀取資源的時候分為@2x和@3x兩種圖片。前者針對非5.5英寸的屏幕尺寸,后者是針對5.5英寸的屏幕尺寸。
?理論上說每個App都應(yīng)真多不同的屏幕尺寸分別存在一套@2x和@3x的圖片,但是在實(shí)際使用中兩套圖片的存在會大大的增加App的體積這是不可取的,因此此框架在資源加載額時候統(tǒng)一使用4.7英寸**(@2x圖)**作為標(biāo)準(zhǔn),向上長寬分別擴(kuò)大1.1倍來適配5.5英寸的屏幕尺寸,向下縮小1.08倍來適配4英寸的屏幕尺寸。
-把圖片資源根據(jù)需要加載的模塊放在根目錄的res文件夾下統(tǒng)一管理
-通過打包腳本build_plist.sh將res下的資源名稱組織成plist文件,其中圖片索引跟世界@2x圖片名稱一一對應(yīng)。
腳本文件build_plist.sh
```shell
#根據(jù)res下文件結(jié)構(gòu),構(gòu)建config.plist
/usr/libexec/PlistBuddy -c "Delete :res" res/resconfig.plist
find res | grep @2x.png$ | while read line
do
if [ "$line" ]
then
path1=${line#res/}
path2=${path1%@2x.png}
path3=${path2//\//_}
image_path=${line%@2x.png}
/usr/libexec/PlistBuddy -c "Add :res:$path3 string \"/$image_path.png\""res/resconfig.plist
fi
done
#/usr/libexec/PlistBuddy -c "Print" res/resconfig.plist
```
通過資源加載UIImageManager類的imageName方法來根據(jù)圖片索引加載實(shí)際對應(yīng)的圖片資源
```objective-c
/*@param imageKey imageKey description
*
*@return UIImage
*/
+ (UIImage *)imageNamed:(NSString *)imageKey
{
NSString *imagePath = [[QHResManager sharedInstance] pathForImage:imageKey];
UIImage *tmpImage = [UIImage imageWithContentsOfFile:imagePath];
if (tmpImage == nil)
{
imagePath = [imagePath stringByReplacingOccurrencesOfString:@".png" withString:@"@2x.png"];
tmpImage = [UIImage imageWithContentsOfFile:imagePath];
}
return tmpImage;
}
```
*資源加載模塊的優(yōu)勢:*
> 1、圖片資源統(tǒng)一管理,資源分類清晰
>
> 2、通過圖片索引加載圖片,避免頻繁書寫@2x和@3x,減少代碼冗余
>
> 3、自動適應(yīng)屏幕的尺寸,免除在使用中需要手動計算圖片大小做屏幕自適應(yīng)
#日志Log模塊
日志系統(tǒng)簡單來說就是在程序運(yùn)行中通過NSLog來打印出來,上下文中關(guān)鍵的信息,方便進(jìn)行程序調(diào)試和問題的追蹤,但是如果僅僅是簡單的打印一句話在實(shí)際的使用中是無法目標(biāo)大型應(yīng)用的要求的,一個成熟的Log系統(tǒng)需要如下功能:
-可以設(shè)定Log等級
-可以積攢到一定量的log后,一次性發(fā)送給服務(wù)器,絕對不能打一個Log就發(fā)一次
-可以一定時間后,將未發(fā)送的log發(fā)送到服務(wù)器
-可以在App切入后臺時將未發(fā)送的log發(fā)送到服務(wù)器
如果要實(shí)現(xiàn)上述的要求,在框架中采用CocoaLumberjack來輔助搭建自己的日志系統(tǒng)。
CocoaLumberjack最早是由[Robbie Hanson ](https://github.com/robbiehanson)開發(fā)的日志庫,可以在iOS和MacOSX開發(fā)上使用。其簡單,快讀,強(qiáng)大又不失靈活。它自帶了幾種log方式,分別是:
- DDASLLogger將log發(fā)送給蘋果服務(wù)器,之后在Console.app中可以查看
- DDTTYLogger將log發(fā)送給Xcode的控制臺
- DDFileLogger講log寫入本地文件
CocoaLumberjack打一個log的流程大概就是這樣的:

所有的log都會發(fā)給DDLog對象,其運(yùn)行在自己的一個GCD隊列(GlobalLoggingQueue),之后,DDLog會將log分發(fā)給其下注冊的一個或多個Logger,這步在多核下是并發(fā)的,效率很高。每個Logger處理收到的log也是在它們自己的GCD隊列下(loggingQueue)做的,它們詢問其下的Formatter,獲取Log消息格式,然后最終根據(jù)Logger的邏輯,將log消息分發(fā)到不同的地方。
因?yàn)橐粋€DDLog可以把log分發(fā)到所有其下注冊的Logger下,也就是說一個log可以同時打到控制臺,打到遠(yuǎn)程服務(wù)器,打到本地文件,相當(dāng)靈活。
CocoaLumberjack支持Log等級:
```objective-c
typedef NS_OPTIONS(NSUInteger, DDLogFlag) {
DDLogFlagError= (1 << 0), // 0...00001
DDLogFlagWarning= (1 << 1), // 0...00010
DDLogFlagInfo= (1 << 2), // 0...00100
DDLogFlagDebug= (1 << 3), // 0...01000
DDLogFlagVerbose= (1 << 4)// 0...10000
};
typedef NS_ENUM(NSUInteger, DDLogLevel) {
DDLogLevelOff= 0,
DDLogLevelError= (DDLogFlagError),// 0...00001
DDLogLevelWarning= (DDLogLevelError| DDLogFlagWarning), // 0...00011
DDLogLevelInfo= (DDLogLevelWarning | DDLogFlagInfo),// 0...00111
DDLogLevelDebug= (DDLogLevelInfo| DDLogFlagDebug),// 0...01111
DDLogLevelVerbose= (DDLogLevelDebug| DDLogFlagVerbose), // 0...11111
DDLogLevelAll= NSUIntegerMax// 1111....11111 (DDLogLevelVerbose plus any other flags)
};
```
DDLogLevel定義了全局的log等級,DDLogFlag是我們打log時設(shè)定的log等級,CocoaLumberjack會比較兩者,如果flag低于level,則不會打log。
DDLogger協(xié)議定義了logger對象需要遵從的方法和變量,為了方便使用,其提供了DDAbstractLogger對象,我們只需要繼承該對象就可以自定義自己的logger。對于第二點(diǎn)和第三點(diǎn)需求,我們可以利用DDAbstractDatabaseLogger,其也是繼承自DDAbstractLogger,并在其上定義了saveThreshold, saveInterval等控制參數(shù)。這個logger本身是針對寫入數(shù)據(jù)庫的log設(shè)計的,我們也可以利用它這幾個參數(shù),實(shí)現(xiàn)我們上面所提的需求的第二和第三點(diǎn)。
對于第二點(diǎn),設(shè)定_saveThreshold值即可,比如如果希望積攢1000條log再一次性發(fā)送,就賦值1000.
對于第三點(diǎn),設(shè)定_saveInterval,比如如果希望每分鐘發(fā)送一次,就設(shè)定60.
由此,CocoaLumberjack已經(jīng)實(shí)現(xiàn)了需求中的1、2、3點(diǎn),我們要做的無非是自定義Logger和Formatter,將log的最終去處改為發(fā)送到我們自己的服務(wù)器中。
而第四點(diǎn),我們可以監(jiān)聽UIApplicationWillResignActiveNotification事件,當(dāng)觸發(fā)時,手動調(diào)用logger的db_save方法,發(fā)送數(shù)據(jù)給服務(wù)器。
#工具處理Util
工具類處理主要包括圖片處理、文件系統(tǒng)、日期、加解密、時間、字符串等常用的工具函數(shù),具體的使用可以參考對應(yīng)的頭文件里邊的函數(shù)注釋。
QHFileUtil文件處理
QHImageUtil圖片處理
QHDateUtil日期處理
QHTimerUtil時間處理
/Encrypt目錄提供AES加解密算法、RSA加解密算法、Base64編碼、MD5編碼、SHA1編碼等算法
# BaseUI
框架的baseUI主要是指的三個提供基礎(chǔ)服務(wù)的組件,分別是控制器基類BaseViewController、導(dǎo)航控制器基類BaseNavigationController和WebView控制器BaseWebViewController。
## BaseViewController
該控件主要對controller的基礎(chǔ)服務(wù)進(jìn)行封裝,在實(shí)際使用過程中所有的業(yè)務(wù)邏輯的controller都應(yīng)該從此類進(jìn)行繼承。它主要封裝如下功能供子類使用:
> - controller樣式定義
> -網(wǎng)絡(luò)模塊的調(diào)用,以及網(wǎng)絡(luò)成功、失敗、處理中回調(diào)函數(shù)的處理
> -頁面切換返回操作pop
> -公告遮罩的顯示、隱藏和等待動畫
> -多線程
> -切換手勢操作
以上功能在基類中給出詳細(xì)的實(shí)現(xiàn),且這些功能都是子類很頻繁使用的業(yè)務(wù)邏輯,將這些業(yè)務(wù)邏輯在基類中實(shí)現(xiàn)可以更好實(shí)現(xiàn)代碼的復(fù)用,減少程序冗余
## BaseNavigationController
BsaeNavigationController繼承自UINavigationController,除了提供必要的頁面跳轉(zhuǎn)邏輯之外,本框架對導(dǎo)航控制器進(jìn)行了定制,主要添加如下功能:
> -對iOS上的interactivePopGestureRecognizer進(jìn)行定制,可以方便打開或者關(guān)閉手勢交互
>
>
> -對導(dǎo)航控制器的Push和Pop進(jìn)行功能定制,使用范圍更廣
實(shí)現(xiàn)UINavigationControllerDelegate的方式,對Push和Pop時的controller的變換進(jìn)行跟蹤,方便后續(xù)的調(diào)試
```objective-c
- (id )navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
DDLogDebug(@"navigationController will push to %@", [toVC class]);
}else if(operation == UINavigationControllerOperationPop){
DDLogDebug(@"navigationController will pop to %@", [toVC class]);
}
return nil;
}
```
## BaseWebViewController
BaseWebViewController是本框架提供出來的加載WebView的控制器,可以認(rèn)為是一個帶有定制化功能的瀏覽器。它采用橋接的模式是WebViewController具有了Javascript交互的能力,具體是指我們可以再瀏覽器中執(zhí)行JavaScript的語句獲取數(shù)據(jù),同時JavaScript也可以執(zhí)行注入在Controller中的OC的函數(shù),獲取OC函數(shù)執(zhí)行的數(shù)據(jù),在Javascript運(yùn)行時中使用,**簡單來說就是可以實(shí)現(xiàn)JS和Native的雙向數(shù)據(jù)傳輸**。具體的使用可以參考如下的部分的內(nèi)容。
#橋接模塊的使用
橋接模塊的主要在iOS端實(shí)現(xiàn)(js -> native interface),安卓的js調(diào)用native的方式非常簡單明了,不禁想如果iOS端也有如此實(shí)現(xiàn)的話,這樣同時即保證安卓,iOS,h5的統(tǒng)一性也能讓開發(fā)者只用關(guān)心交互的接口即可。因此框架便引如了`EasyJSWebView`的第三方的框架(基于說明2設(shè)計),我們不在此過多的論述EasyJSWebView的橋接原理,只說明在框架中如何使用。
雖然在iOS7以后iOS提供了`JavaScriptCore Framework`來進(jìn)行JS的控制,但是卻存在:
> - iOS端雖然也可以通過`JSContext`注入全局的方法但是達(dá)不到與安卓端統(tǒng)一
> - iOS端可以通過攔截h5請求的url,通過url的格式區(qū)分類或方法,但是這樣不夠直觀,也達(dá)不到與安卓端統(tǒng)一
因此我們在本框架中采用EasyJSWebView來實(shí)現(xiàn)橋接,具體步驟簡單描述如下:
-在JSInterface類中定義Native方法供JS調(diào)用
-將JSInterface類作為接口同addInterface注入到EasyJSWebView類中
-將EasyJSWebView的對象作為SubView添加到BaseWebViewController中
這樣就可以再當(dāng)前BaseWebViewController加載的JS上下文環(huán)境中調(diào)用Native的方法了。
EasyJSWebView的代碼參考:https://github.com/dukeland/EasyJSWebView
# WebSocket
?WebSocket消息就是指的App跟服務(wù)端的異步通知消息,客戶端通過WebSocket協(xié)議同遠(yuǎn)程服務(wù)器之間建立一個長連接,通過此連接,服務(wù)器可以向客戶端主動推送消息,客戶端亦可以向服務(wù)器發(fā)送請求,通過WebSocket連接,客戶端可以在請求發(fā)出后,等待服務(wù)器端發(fā)送異步處理的結(jié)果消息,避免主動查詢,減少等待時間,根據(jù)異步返回的消息來進(jìn)行下一步處理。
Websocket連接建立、銷毀
異步消息通知的基礎(chǔ)是WebSocket連接的建立,但是由于移動客戶端的諸多限制,一直維持WebSocket連接的耗費(fèi)太大,包括電量、流量等,因此websocket連接的建立和銷毀僅限以下場景(WebSocket連接的建立、銷毀對于用戶是無感知的):
-用戶登錄、登出
用戶登錄包括用戶名密碼登陸、手勢密碼登陸、一賬通登陸。在未登陸前,客戶端同服務(wù)器是沒有WebSocket連接的,因此不會有任何消息提示。在登陸后,客戶端會主動去創(chuàng)建WebSocket連接。相應(yīng)的,用戶主動登出后,WebSocket連接被銷毀。
-應(yīng)用切換
應(yīng)用切換是指應(yīng)用進(jìn)入后臺,或者從后臺喚醒,包括鎖屏、點(diǎn)擊home鍵(iOS)等,當(dāng)應(yīng)用進(jìn)入后臺時,連接被銷毀,當(dāng)應(yīng)用喚醒時,如果已登陸,創(chuàng)建連接。
-連接建立失敗處理
當(dāng)WebSocket連接首次創(chuàng)建失敗時,客戶端會進(jìn)行重試,重試的最大次數(shù)為3,每次重試之間的間隔為15秒。
本框架對第三方框架`SRWebSocket`()進(jìn)行的定制化的封裝,通過QHWebSocket類初始化創(chuàng)建連接對象,
連接服務(wù)端
```objective-c
-(void)connect;
```
發(fā)送心跳
```objective-c
-(void)startHeartBeat;
```
發(fā)送數(shù)據(jù)
```objective-c
-(void)sendText:(NSString*)text;
-(void)sendData:(NSData*)data;
```
斷開服務(wù)器連接并銷毀
```objective-c
-(void)destroy;
```
WebSocket跟服務(wù)器的反饋,以及服務(wù)端下行通道的數(shù)據(jù)獲取通過對應(yīng)代理來實(shí)現(xiàn):
```objective-c
@protocol QHWebSocketDelegate
- (void)webSocket:(QHWebSocket *)webSocket didReceiveTextMessage:(NSString*)message;
- (void)webSocket:(QHWebSocket *)webSocket didReceiveBinaryMessage:(NSData*)message;
@optional
- (void)webSocketDidOpen:(QHWebSocket *)webSocket;
- (void)webSocket:(QHWebSocket *)webSocket didFailWithError:(NSError *)error;
- (void)webSocket:(QHWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
- (void)webSocket:(QHWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
- (void)applicationDidEnterBackground;
- (void)applicationWillEnterForeground;
@end
```
服務(wù)端通過WebSocket下行通知我們可以在客戶端App實(shí)現(xiàn)一些比較特殊的控制功能;例如:
-登陸互踢
?登陸互踢,是當(dāng)用戶在一臺設(shè)備A上已經(jīng)登陸,然后該賬號在另一臺設(shè)備B上又登陸了,這時,如果websocket連接保持連接狀態(tài),服務(wù)器會向A設(shè)備發(fā)送一條登陸互踢消息,然后A設(shè)備上應(yīng)用就會提示用戶賬號在其他設(shè)備登陸。

-會話超時
?會話超時,是在客戶端建立websocket連接時,服務(wù)器可能返回的消息之一,譬如當(dāng)應(yīng)用切換至后臺時,websocket斷開連接,然后一段時間后,重新切回應(yīng)用,此時客戶端重新建立websocket連接,但是會話已超時,服務(wù)器端就會返回會話超時消息,客戶端接收到消息后就會彈出手勢密碼(如果有)重新登陸,或者跳往未登錄前首頁。
# UI組件
本框架封裝了很多iOS常用的UI組件,可以方便的修改和復(fù)用
公告彈框----AlertView
手勢密碼組件----GesturePassword
輸入框組件-----InputField(包括文字、純數(shù)字、密碼等等)
Toast組件-----MessageToast
輪播組件-----RecycleScrollView(常用于輪播廣告)
密保鍵盤------SecurityKeyboard
下拉刷新組件-----QHTableViewRefresher(列表下拉刷新的封裝)
公告等待層遮罩------WaitView
App啟動的廣告Splash-----SplashView
公共的控件工廠類-----ViewFactory(根據(jù)樣式定義公共的Button、Label、Navbar等基礎(chǔ)UI)
ViewFactory的部分工廠方法的示例:
```objective-c
// button類型
typedef NS_ENUM(NSUInteger, BNButtonType) {
BNButtonTypeDefault,
BNButtonTypeNavBarDarkBack,
BNButtonTypeNavBarDarkClose
};
// NavBar類型
typedef NS_ENUM(NSUInteger, BNNavigationBarType) {
BNNavigationBarTypeDefault
};
//label類型
typedef NS_ENUM(NSUInteger, BNLabelType) {
BNLabelTypeDefault,
BNLabelTypeWhite,
BNLabelTypeDark
};
+ (UIImageView *)navigationBarWithType:(BNNavigationBarType)type;
+ (UILabel *)labelWithType:(BNLabelType)type;
+ (UIButton *)buttonWithType:(BNButtonType)type;
```
# React Native支持
React Native是一種利用Javascript和React的技術(shù)來描述Native頁面布局的開發(fā)方式,我們簡要的描述一下在現(xiàn)有的iOS工程添加React Native支持的方式,由于該開發(fā)對H5的開發(fā)要求較高,如果你對React技術(shù)不熟悉建議先參考相關(guān)文檔。
方式一:直接創(chuàng)建React Native的工程
例用faceBook提供的React Native開發(fā)通過react-native init可以創(chuàng)建一個純使用react開發(fā)的工程,但是在實(shí)際中我們不可能完全用React來進(jìn)行App的開發(fā),因此此方式適用范圍比較窄。
關(guān)于環(huán)境的搭建請參考:
http://reactnative.cn/docs/0.40/getting-started.html
方式二:在現(xiàn)有的iOS工程中添加React Native的支持
1、在工程文件根目錄創(chuàng)建package.json
2、在使用過程中可以根據(jù)實(shí)際需要修改配置文件package.json,本文示例只添加了react和react native兩個版本的node依賴包。
```json
{
"dependencies":{
"react":"15.4.1",
"react-native": "0.42.0"
}
}
```
3、使用npm(node包管理器,Node package manager)來安裝React和React Native模塊。這些模塊會被安裝到項(xiàng)目根目錄下的`node_modules/`目錄中。在包含有package.json文件的目錄(一般也就是項(xiàng)目根目錄)中運(yùn)行下列命令來安裝:
```
$ npm install
```
在安裝成功后,可以再根目錄看到node_module的文件夾,這個就是react native所有的依賴庫文件。
4、按照如下圖在項(xiàng)目工程中添加react native的第三方庫文件,當(dāng)完成如下圖所示的工程依賴并編譯通過就表示當(dāng)前的工程已經(jīng)開始使用React Native進(jìn)行開發(fā)了。

5、編譯工程文件,解決庫依賴文件,這樣支持React Native的文件就搭建完畢。
然后在根目錄創(chuàng)建React Native的入口文件index.ios.js(名稱不能改動),就可以進(jìn)行相關(guān)開發(fā)了,
關(guān)于React Native的語法和文檔可以參考如下網(wǎng)站:
http://reactnative.cn/
http://nav.react-china.org/
https://github.com/reactnativecn/react-native-guide
index.ios.js的示例,在RCTView中用React顯示一個Label文字
```javascript
"use strict"
import React from 'react'
import {
AppRegistery,
StyleSheet,
Text,
View,
NavigatorIOS
} from 'react-native'
class RNView extends React.Component{
render() {
var param = this.props.param;
return (
Text {{param}}
)
},
var styles = StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center'
backgroundColor:'#FFFFFF'
}
labelText:{
fontSize:10
textAlign:'center'
color:'#333333'
}
})
}
AppRegistery.registerComponent('RNView', () => lo-view-ios);
```