秦 業(yè),高建華
(上海師范大學(xué) 計算機(jī)科學(xué)與工程系,上海 200234)
隨著智能終端普及率的不斷提高,生活中的很多問題都能用App來解決。對于生活社區(qū),一款能解決用戶金融理財、生活服務(wù)、繳費的App必不可少,因此文中推出了社區(qū)金融App產(chǎn)品。該App功能包括手機(jī)充值、代繳水煤電費;在線問診、預(yù)約、掛號、咨詢;查閱購買基金、股票、黃金等金融理財產(chǎn)品。
IOS系統(tǒng)主要用在iPhone、iPad等產(chǎn)品[1],是移動端最受歡迎的操作系統(tǒng)。IOS App開發(fā)采用Xcode,它集成了各個版本的模擬器,適合開發(fā)iPhone、iPad、等蘋果公司產(chǎn)品的應(yīng)用。IOS系統(tǒng)基于FreeBSD系統(tǒng),從本質(zhì)上說IOS是Unix的一個分支,特點主要體現(xiàn)在后臺運(yùn)行機(jī)制上,包括三個方面:
(1)IOS系統(tǒng)有獨特的任務(wù)管理機(jī)制。當(dāng)應(yīng)用程序不在前臺運(yùn)行時,除了部分服務(wù),其他應(yīng)用在10分鐘后都被系統(tǒng)掛起。被掛起等同于不執(zhí)行,只是數(shù)據(jù)駐留在內(nèi)存而已。
(2)內(nèi)存管理機(jī)制。在執(zhí)行任意應(yīng)用時,應(yīng)用向系統(tǒng)申請內(nèi)存空間,如果應(yīng)用在使用的過程中不斷申請內(nèi)存,超過了系統(tǒng)限定的內(nèi)存區(qū)間,系統(tǒng)會發(fā)出內(nèi)存警報,嚴(yán)重時會直接將應(yīng)用殺死。同樣,如果應(yīng)用向系統(tǒng)申請內(nèi)存時,系統(tǒng)內(nèi)存空間不足,系統(tǒng)會結(jié)束后臺應(yīng)用的運(yùn)行,以釋放空間資源[2]。
(3)偽多任務(wù)。例如微信,退出后不在后臺運(yùn)行。用戶收到消息是因為系統(tǒng)推送服務(wù)。無論用戶的應(yīng)用程序是否運(yùn)行,IOS都會在后臺維護(hù)這個服務(wù)實現(xiàn)偽多任務(wù),所有應(yīng)用程序共用這一服務(wù)。
IOS App開發(fā)主要有Native App、Hybrid App、Web App[3]等三種開發(fā)架構(gòu),各自特點如表1所示。
表1 三種IOS開發(fā)架構(gòu)比較
該項目根據(jù)用戶體驗選擇Native App作為開發(fā)架構(gòu)。
在手機(jī)平臺上,手指觸摸不是一個精確點擊,而是一個“塊”狀的點擊范圍,在范圍內(nèi)的控件會被點擊操作激發(fā),這點和鼠標(biāo)完全不一樣。因此在功能設(shè)計中,“塊”的設(shè)計相當(dāng)重要?;谀壳皹I(yè)界主流的頁面設(shè)計,結(jié)合UITabbarController[4]和UITableViewController實現(xiàn)需求。前者可以將屏幕底部等分或自定義分成項目需要的模塊,用來布置項目中最常用的幾個功能模塊;后者則能夠以類似表格的形式展現(xiàn)每個功能模塊對應(yīng)的內(nèi)容。
IOS App開發(fā)分為客戶端和服務(wù)器端開發(fā),是一個典型的C/S程序,計算工作由服務(wù)器端完成,客戶端實現(xiàn)GUI界面的展示、數(shù)據(jù)的獲取和解析、用戶操作的捕捉等。這種前后端分離的開發(fā)模式有利于各司其職和實現(xiàn)松耦合,開發(fā)出更好的應(yīng)用。文中專注客戶端開發(fā)過程中的部分關(guān)鍵技術(shù),主要包括MVVM設(shè)計模式、JSON數(shù)據(jù)解析、多線程編程技術(shù)和緩存機(jī)制技術(shù)。
MVVM模式是Model-View-ViewMode模式的簡稱。由視圖(View)、視圖模型(ViewModel)、模型(Model)三部分組成。通過這三部分實現(xiàn)UI邏輯、呈現(xiàn)邏輯和狀態(tài)控制、數(shù)據(jù)與業(yè)務(wù)邏輯的分離[5]。
Model層代表了描述業(yè)務(wù)邏輯和數(shù)據(jù)的一系列類的集合。它也定義了數(shù)據(jù)修改和操作的業(yè)務(wù)規(guī)則。
View代表了UI組件,像CSS、Jquery、html等,只負(fù)責(zé)展示從Presenter接收到的數(shù)據(jù),也就是把模型轉(zhuǎn)化成UI。
View Model負(fù)責(zé)暴露方法,命令,其他屬性來操作View的狀態(tài),組裝Model作為View動作的結(jié)果,并且觸發(fā)View自己的事件。
MVVM的技術(shù)關(guān)鍵點是:
(1)用戶只通過View和服務(wù)端交互信息。
(2)View和ViewModel是多對一關(guān)系。意味著一個ViewModel只映射多個View(在MVC中一個View對應(yīng)一個ViewController)。
(3)View持有ViewModel的引用,但是ViewModel沒有任何View的信息。
(4)View和ViewModel之間有著雙向數(shù)據(jù)綁定關(guān)系。
三個組件之間的關(guān)系如圖1所示。
MVVM這種新的設(shè)計模式真正做到了將頁面與數(shù)據(jù)邏輯分離,解決了傳統(tǒng)MVC模式中Controller過于臃腫的問題,并且可以將使用次數(shù)多的視圖邏輯放到ViewModel中,通過一對多的映射關(guān)系供更多的View重用,提高了復(fù)用性。
客戶端向服務(wù)器端發(fā)送數(shù)據(jù)請求,服務(wù)器端響應(yīng)后返回XML或者JSON格式的數(shù)據(jù)。由于JSON格式是一種輕量級數(shù)據(jù)交互格式[6],目前已經(jīng)取代XML,該項目也采用JSON格式。
JSON解析指的是將服務(wù)器端傳來的JSON格式數(shù)據(jù)轉(zhuǎn)化為IOS端顯示的模型數(shù)據(jù),并將JSON中各個屬性數(shù)據(jù)賦值給模型對應(yīng)屬性。從IOS5開始,Xcode原生JSON解析類庫NSJSONSerialization,但在實際開發(fā)中,NSJSONSerialization使用難度大、易出錯。Github有許多類似的第三方類庫更加優(yōu)秀,有YYModel、MJExtension等,其中YYModel性能更好,使用起來更加簡單方便。因此該項目使用YYModel解析JSON數(shù)據(jù)。
YYModel是通過對NSObject的部分內(nèi)容進(jìn)行封裝來實現(xiàn)功能,具體是:
YYClassInfo是對Class進(jìn)行封裝描述:
·YYClassIvarInfo對Class的Ivar進(jìn)行封裝描述;
·YYClassMethodInfo對Class的Method進(jìn)行封裝描述;
·YYClassPropertyInfo對Class的Property進(jìn)行封裝描述。
YYModel是對YYClassInfo進(jìn)行封裝,并暴露調(diào)用接口給用戶,具體是:
·YYModelMeta對YYClassInfo進(jìn)行封裝描述;
·YYModelPropertyMeta對YYClassProperty進(jìn)行封裝描述。
其中主要提供了三種解析類別:
(1)NSObject(YYModel):提供一些字典模型互轉(zhuǎn)的方法,將對key/value進(jìn)行匹配,賦值給Model對應(yīng)的property。
(2)NSArray(YYModel):為NSArray提供字典轉(zhuǎn)模型的方法。
(3)NSDictionary(YYModel):為NSDictionary提供字典轉(zhuǎn)模型方法。
下面以用戶點擊加載“股票信息”為例,具體的解析流程如圖2所示。
對應(yīng)加載股票信息模塊的部分代碼如下:
//result表示服務(wù)器端返回的JSON數(shù)據(jù),先保存到字典當(dāng)中
NSDictionary *dic=result[@"singleData"][0];
//StockDetail調(diào)用YYModel里的yy_modelWithJSON方法,返回一個StockDetail模型對象
StockDetail *detail=[StockDetail yy_modelWithJSON:dic];
self.stockDetail=detail;
//YYModel中的yy_modelWithJSON方法將JSON數(shù)據(jù)轉(zhuǎn)化成預(yù)定義好的StockDetail對象
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic=[self _yy_dictionaryWithJSON:json];
//將StockDetail對象賦值給模型中的self.stockDetail,顯示到界面上
return [self yy_modelWithDictionary:dic];}
上述代碼是通過AFNetworking實現(xiàn)HTTP或HTTPS鏈接請求,獲取后端API返回的JSON字符串[7],再通過YYModel實現(xiàn)將JSON字符串轉(zhuǎn)化為NSDIctionary格式的模型對象,最后將模型的數(shù)據(jù)顯示到界面上。
圖2 獲取股票JSON流程
實際開發(fā)中,主線程主要負(fù)責(zé)完成主控制器的調(diào)用、控制和控制器之間的通信任務(wù),而控制器所控制的視圖上的數(shù)據(jù)獲取則交給其他線程完成。因為線程中任務(wù)的執(zhí)行是順序執(zhí)行的,也就是說在一個時間段內(nèi)一個線程只能執(zhí)行一個任務(wù),對于需要同時執(zhí)行的任務(wù)就需要多線程并發(fā)執(zhí)行[8]。
IOS中實現(xiàn)多線程的方式有很多種,包括NSThread、NSOperation等。但最常用的是GCD。GCD是一個在后端管理線程池的工具[9],讓開發(fā)人員無需和線程直接打交道。
GCD是基于C語言的底層API,其中最重要的概念就是dispatch_queue,它是一個對象,可以接受任務(wù)并以先到先執(zhí)行的方式處理任務(wù)。根據(jù)GCD隊列的種類處理方式有所不同,最主要的有以下2種:
Serial:串行隊列以先進(jìn)先出(FIFO)的順序執(zhí)行任務(wù),所以串行隊列經(jīng)常用來做訪問某些特定資源的同步處理??筛鶕?jù)需要創(chuàng)建多個隊列,而這些隊列相對其他隊列都是并發(fā)執(zhí)行的。即若創(chuàng)建了4個串行隊列,每一個隊列在同一時間都只執(zhí)行一個任務(wù),對這四個任務(wù)來說,它們是相互獨立且并發(fā)執(zhí)行的。如果需要創(chuàng)建串行隊列,一般用dispatch_queue_create這個方法來實現(xiàn)。
Concueerent:并發(fā)隊列雖然是能同時執(zhí)行多個任務(wù),但這些任務(wù)仍然是按照先到先執(zhí)行(FIFO)的順序來執(zhí)行的。并發(fā)隊列會基于系統(tǒng)負(fù)載來合適地選擇并發(fā)執(zhí)行這些任務(wù)。而在iOS5之后,也可以用dispatch_queue_create,并指定隊列類型為DISPATCH_QUEUE_CONCURRENT來自己創(chuàng)建一個并發(fā)隊列。
創(chuàng)建需要的隊列后,需要將其添加到任務(wù)當(dāng)中,這又分為同步和異步兩種。一般情況下,使用dispatch_async和dispatch_async_f來執(zhí)行異步操作。比如,添加一個block對象或C函數(shù)到一個隊列后就會立即返回,任務(wù)會由GCD決定執(zhí)行順序,以及任務(wù)執(zhí)行完畢時間。好處是,若需要在后臺執(zhí)行一個基于網(wǎng)絡(luò)或CPU密集型任務(wù),使用異步方法不會阻塞當(dāng)前線程[10]。
盡管一般情況下,優(yōu)先選擇異步操作,但是在某些情況下,還是需要任務(wù)同步來執(zhí)行。比如需要用同步操作來防止資源競爭或其他同步問題。這時可以用dispatch_sync和dispatch_sync_f方法把任務(wù)添加到隊列中,這樣被添加的任務(wù)會阻塞當(dāng)前線程,直到這些任務(wù)執(zhí)行完,確保同一資源在同一時間只能被一個線程訪問到。
以用戶點擊“詳情”獲取對應(yīng)股票的詳情信息為例,主線程在獲取視圖控制器和文字內(nèi)容時,子線程負(fù)責(zé)加載圖片,這樣可以做到在加載圖片的過程中不會出現(xiàn)卡頓現(xiàn)象,步驟如圖3所示。
圖3 YYCache結(jié)構(gòu)關(guān)系圖
當(dāng)然,GCD的使用場景很多,該項目在清理緩存信息時使用GCD異步刪除本機(jī)的緩存,部分實現(xiàn)代碼如下:
//判斷緩存文件路徑是否存在
if (![path isEqualToString:_cachePath]) {
//調(diào)用GCD,開啟異步線程,傳遞線程隊列和回調(diào)函數(shù)
dispatch_async(_queue, ^{
//將字符串路徑轉(zhuǎn)化為IOS識別的文件路徑
NSURL *fileURL=[NSURL URLWithString:path];
NSDictionary *dictionary=[fileURL
resourceValuesForKeys:@[NSURLContentModificationDateKey] error:nil];
//得到內(nèi)容的最后修改時間
NSDate *modificationDate=[dictionary objectForKey:NSURLContentModificationDateKey];
if (modificationDate.timeIntervalSince1970 - date.timeIntervalSince1970 <0) {
[_fileManager removeItemAtPath:fileURL.absoluteString error:nil];
//從文件路徑和緩存中刪除緩存文件
[_memoryCache removeObjectForKey:fileURL.lastPathComponent];}});}
在App的使用過程中緩存機(jī)制是必不可少的,根據(jù)存儲的不同緩存也可以分為內(nèi)存緩存和文件緩存[11],分別具有高速度低容量和低速度高容量的特點。YYCache同時具有這兩種緩存機(jī)制,項目選擇YYCache作為緩存框架。
YYCache中內(nèi)存緩存和文件緩存的關(guān)系如圖4所示。
圖4 YYCache結(jié)構(gòu)關(guān)系圖
內(nèi)存緩存是初次請求數(shù)據(jù)時將獲取到的數(shù)據(jù)存放到內(nèi)存當(dāng)中,當(dāng)用戶再次訪問到該數(shù)據(jù)時無需再向服務(wù)器請求數(shù)據(jù),直接從內(nèi)存中獲取緩存數(shù)據(jù),具有高速度的特點,但由于內(nèi)存較為昂貴,所以容量較低。
文件緩存是將獲得的數(shù)據(jù)存放在客戶端數(shù)據(jù)庫文件中,社區(qū)金融App使用了SQLite數(shù)據(jù)庫做文件緩存。在用戶初次請求服務(wù)器獲得數(shù)據(jù)時,將音樂、圖片等大容量文件按照鍵值對的形式存放到SQLite.db文件中,當(dāng)客戶端再次訪問相同頁面時,控制器會先檢測是否有對應(yīng)的db文件,若無則發(fā)送請求給服務(wù)器,并將返回的數(shù)據(jù)緩存到SQLite中[12],若有則直接從db文件中取得數(shù)據(jù)并顯示在界面上。
在請求社區(qū)金融新聞信息時,可能同時存在內(nèi)存緩存和文件緩存,使用YYCache得到的緩存流程如圖5所示。
在獲取金融類新聞信息時使用YYCache的部分代碼如下所示:
//初始化YYCache
YYCache *cache=[YYCache cacheWithName:@"mydb"];
//緩存普通字符
[cache setObject:@"中國中車" forKey:@"name"];
NSString *name=(NSString*)[cache objectForKey:@"name"];
NSLog(@"name: %@",name);
//緩存模型
[cache setObject:(id)model forKey:@"user"];
//緩存數(shù)組
NSMutableArray *array=@[].mutableCopy;
For (NSInteger i=0;i<10;i++) {
[array addObject:model];
}
//異步緩存
[cache setObject:array forKey:@"user" withBlock:^{
// 異步回調(diào)
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"array緩存完成....");
}];
//延時讀取
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(0.3*NSEC_PER_SEC)),dispatch_get_main_queue(),^{
//異步讀取
[cache objectForKey:@"user" withBlock:^(NSString*_Nonnull key,id_Nonnull object){
//異步回調(diào)
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",object);}];});
圖5 獲取緩存流程
內(nèi)存緩存和文件緩存的區(qū)別在于,當(dāng)應(yīng)用還在進(jìn)程中時,應(yīng)用有內(nèi)存緩存和文件緩存兩種緩存方式,當(dāng)應(yīng)用進(jìn)程被殺死時,應(yīng)用只存在文件緩存,內(nèi)存緩存將被清空。因此對于圖片、音頻、視頻等大容量文件,App使用了文件緩存技術(shù)將數(shù)據(jù)緩存到SQLite數(shù)據(jù)庫中,節(jié)省資源;對于文字等小容量內(nèi)容則緩存到內(nèi)存中,方便獲取[13]。
為了更好地適應(yīng)移動互聯(lián)網(wǎng)時代,社區(qū)金融IOS端App將不斷更新維護(hù),未來開發(fā)的重點在于功能模塊的擴(kuò)展,讓App滿足更多理財需求??紤]到移動開發(fā)成本問題,統(tǒng)一IOS和安卓App的跨平臺開發(fā)[14]是大勢所趨,因此從Native App到Hybrid App的遷移是以后工作的重點。