秦 業,高建華
(上海師范大學 計算機科學與工程系,上海 200234)
隨著智能終端普及率的不斷提高,生活中的很多問題都能用App來解決。對于生活社區,一款能解決用戶金融理財、生活服務、繳費的App必不可少,因此文中推出了社區金融App產品。該App功能包括手機充值、代繳水煤電費;在線問診、預約、掛號、咨詢;查閱購買基金、股票、黃金等金融理財產品。
IOS系統主要用在iPhone、iPad等產品[1],是移動端最受歡迎的操作系統。IOS App開發采用Xcode,它集成了各個版本的模擬器,適合開發iPhone、iPad、等蘋果公司產品的應用。IOS系統基于FreeBSD系統,從本質上說IOS是Unix的一個分支,特點主要體現在后臺運行機制上,包括三個方面:
(1)IOS系統有獨特的任務管理機制。當應用程序不在前臺運行時,除了部分服務,其他應用在10分鐘后都被系統掛起。被掛起等同于不執行,只是數據駐留在內存而已。
(2)內存管理機制。在執行任意應用時,應用向系統申請內存空間,如果應用在使用的過程中不斷申請內存,超過了系統限定的內存區間,系統會發出內存警報,嚴重時會直接將應用殺死。同樣,如果應用向系統申請內存時,系統內存空間不足,系統會結束后臺應用的運行,以釋放空間資源[2]。
(3)偽多任務。例如微信,退出后不在后臺運行。用戶收到消息是因為系統推送服務。無論用戶的應用程序是否運行,IOS都會在后臺維護這個服務實現偽多任務,所有應用程序共用這一服務。
IOS App開發主要有Native App、Hybrid App、Web App[3]等三種開發架構,各自特點如表1所示。

表1 三種IOS開發架構比較
該項目根據用戶體驗選擇Native App作為開發架構。
在手機平臺上,手指觸摸不是一個精確點擊,而是一個“塊”狀的點擊范圍,在范圍內的控件會被點擊操作激發,這點和鼠標完全不一樣。因此在功能設計中,“塊”的設計相當重要?;谀壳皹I界主流的頁面設計,結合UITabbarController[4]和UITableViewController實現需求。前者可以將屏幕底部等分或自定義分成項目需要的模塊,用來布置項目中最常用的幾個功能模塊;后者則能夠以類似表格的形式展現每個功能模塊對應的內容。
IOS App開發分為客戶端和服務器端開發,是一個典型的C/S程序,計算工作由服務器端完成,客戶端實現GUI界面的展示、數據的獲取和解析、用戶操作的捕捉等。這種前后端分離的開發模式有利于各司其職和實現松耦合,開發出更好的應用。文中專注客戶端開發過程中的部分關鍵技術,主要包括MVVM設計模式、JSON數據解析、多線程編程技術和緩存機制技術。
MVVM模式是Model-View-ViewMode模式的簡稱。由視圖(View)、視圖模型(ViewModel)、模型(Model)三部分組成。通過這三部分實現UI邏輯、呈現邏輯和狀態控制、數據與業務邏輯的分離[5]。
Model層代表了描述業務邏輯和數據的一系列類的集合。它也定義了數據修改和操作的業務規則。
View代表了UI組件,像CSS、Jquery、html等,只負責展示從Presenter接收到的數據,也就是把模型轉化成UI。
View Model負責暴露方法,命令,其他屬性來操作View的狀態,組裝Model作為View動作的結果,并且觸發View自己的事件。
MVVM的技術關鍵點是:
(1)用戶只通過View和服務端交互信息。
(2)View和ViewModel是多對一關系。意味著一個ViewModel只映射多個View(在MVC中一個View對應一個ViewController)。
(3)View持有ViewModel的引用,但是ViewModel沒有任何View的信息。
(4)View和ViewModel之間有著雙向數據綁定關系。
三個組件之間的關系如圖1所示。
MVVM這種新的設計模式真正做到了將頁面與數據邏輯分離,解決了傳統MVC模式中Controller過于臃腫的問題,并且可以將使用次數多的視圖邏輯放到ViewModel中,通過一對多的映射關系供更多的View重用,提高了復用性。
客戶端向服務器端發送數據請求,服務器端響應后返回XML或者JSON格式的數據。由于JSON格式是一種輕量級數據交互格式[6],目前已經取代XML,該項目也采用JSON格式。
JSON解析指的是將服務器端傳來的JSON格式數據轉化為IOS端顯示的模型數據,并將JSON中各個屬性數據賦值給模型對應屬性。從IOS5開始,Xcode原生JSON解析類庫NSJSONSerialization,但在實際開發中,NSJSONSerialization使用難度大、易出錯。Github有許多類似的第三方類庫更加優秀,有YYModel、MJExtension等,其中YYModel性能更好,使用起來更加簡單方便。因此該項目使用YYModel解析JSON數據。
YYModel是通過對NSObject的部分內容進行封裝來實現功能,具體是:
YYClassInfo是對Class進行封裝描述:
·YYClassIvarInfo對Class的Ivar進行封裝描述;
·YYClassMethodInfo對Class的Method進行封裝描述;
·YYClassPropertyInfo對Class的Property進行封裝描述。
YYModel是對YYClassInfo進行封裝,并暴露調用接口給用戶,具體是:
·YYModelMeta對YYClassInfo進行封裝描述;
·YYModelPropertyMeta對YYClassProperty進行封裝描述。
其中主要提供了三種解析類別:
(1)NSObject(YYModel):提供一些字典模型互轉的方法,將對key/value進行匹配,賦值給Model對應的property。
(2)NSArray(YYModel):為NSArray提供字典轉模型的方法。
(3)NSDictionary(YYModel):為NSDictionary提供字典轉模型方法。
下面以用戶點擊加載“股票信息”為例,具體的解析流程如圖2所示。
對應加載股票信息模塊的部分代碼如下:
//result表示服務器端返回的JSON數據,先保存到字典當中
NSDictionary *dic=result[@"singleData"][0];
//StockDetail調用YYModel里的yy_modelWithJSON方法,返回一個StockDetail模型對象
StockDetail *detail=[StockDetail yy_modelWithJSON:dic];
self.stockDetail=detail;
//YYModel中的yy_modelWithJSON方法將JSON數據轉化成預定義好的StockDetail對象
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic=[self _yy_dictionaryWithJSON:json];
//將StockDetail對象賦值給模型中的self.stockDetail,顯示到界面上
return [self yy_modelWithDictionary:dic];}
上述代碼是通過AFNetworking實現HTTP或HTTPS鏈接請求,獲取后端API返回的JSON字符串[7],再通過YYModel實現將JSON字符串轉化為NSDIctionary格式的模型對象,最后將模型的數據顯示到界面上。

圖2 獲取股票JSON流程
實際開發中,主線程主要負責完成主控制器的調用、控制和控制器之間的通信任務,而控制器所控制的視圖上的數據獲取則交給其他線程完成。因為線程中任務的執行是順序執行的,也就是說在一個時間段內一個線程只能執行一個任務,對于需要同時執行的任務就需要多線程并發執行[8]。
IOS中實現多線程的方式有很多種,包括NSThread、NSOperation等。但最常用的是GCD。GCD是一個在后端管理線程池的工具[9],讓開發人員無需和線程直接打交道。
GCD是基于C語言的底層API,其中最重要的概念就是dispatch_queue,它是一個對象,可以接受任務并以先到先執行的方式處理任務。根據GCD隊列的種類處理方式有所不同,最主要的有以下2種:
Serial:串行隊列以先進先出(FIFO)的順序執行任務,所以串行隊列經常用來做訪問某些特定資源的同步處理。可根據需要創建多個隊列,而這些隊列相對其他隊列都是并發執行的。即若創建了4個串行隊列,每一個隊列在同一時間都只執行一個任務,對這四個任務來說,它們是相互獨立且并發執行的。如果需要創建串行隊列,一般用dispatch_queue_create這個方法來實現。
Concueerent:并發隊列雖然是能同時執行多個任務,但這些任務仍然是按照先到先執行(FIFO)的順序來執行的。并發隊列會基于系統負載來合適地選擇并發執行這些任務。而在iOS5之后,也可以用dispatch_queue_create,并指定隊列類型為DISPATCH_QUEUE_CONCURRENT來自己創建一個并發隊列。
創建需要的隊列后,需要將其添加到任務當中,這又分為同步和異步兩種。一般情況下,使用dispatch_async和dispatch_async_f來執行異步操作。比如,添加一個block對象或C函數到一個隊列后就會立即返回,任務會由GCD決定執行順序,以及任務執行完畢時間。好處是,若需要在后臺執行一個基于網絡或CPU密集型任務,使用異步方法不會阻塞當前線程[10]。
盡管一般情況下,優先選擇異步操作,但是在某些情況下,還是需要任務同步來執行。比如需要用同步操作來防止資源競爭或其他同步問題。這時可以用dispatch_sync和dispatch_sync_f方法把任務添加到隊列中,這樣被添加的任務會阻塞當前線程,直到這些任務執行完,確保同一資源在同一時間只能被一個線程訪問到。
以用戶點擊“詳情”獲取對應股票的詳情信息為例,主線程在獲取視圖控制器和文字內容時,子線程負責加載圖片,這樣可以做到在加載圖片的過程中不會出現卡頓現象,步驟如圖3所示。

圖3 YYCache結構關系圖
當然,GCD的使用場景很多,該項目在清理緩存信息時使用GCD異步刪除本機的緩存,部分實現代碼如下:
//判斷緩存文件路徑是否存在
if (![path isEqualToString:_cachePath]) {
//調用GCD,開啟異步線程,傳遞線程隊列和回調函數
dispatch_async(_queue, ^{
//將字符串路徑轉化為IOS識別的文件路徑
NSURL *fileURL=[NSURL URLWithString:path];
NSDictionary *dictionary=[fileURL
resourceValuesForKeys:@[NSURLContentModificationDateKey] error:nil];
//得到內容的最后修改時間
NSDate *modificationDate=[dictionary objectForKey:NSURLContentModificationDateKey];
if (modificationDate.timeIntervalSince1970 - date.timeIntervalSince1970 <0) {
[_fileManager removeItemAtPath:fileURL.absoluteString error:nil];
//從文件路徑和緩存中刪除緩存文件
[_memoryCache removeObjectForKey:fileURL.lastPathComponent];}});}
在App的使用過程中緩存機制是必不可少的,根據存儲的不同緩存也可以分為內存緩存和文件緩存[11],分別具有高速度低容量和低速度高容量的特點。YYCache同時具有這兩種緩存機制,項目選擇YYCache作為緩存框架。
YYCache中內存緩存和文件緩存的關系如圖4所示。

圖4 YYCache結構關系圖
內存緩存是初次請求數據時將獲取到的數據存放到內存當中,當用戶再次訪問到該數據時無需再向服務器請求數據,直接從內存中獲取緩存數據,具有高速度的特點,但由于內存較為昂貴,所以容量較低。
文件緩存是將獲得的數據存放在客戶端數據庫文件中,社區金融App使用了SQLite數據庫做文件緩存。在用戶初次請求服務器獲得數據時,將音樂、圖片等大容量文件按照鍵值對的形式存放到SQLite.db文件中,當客戶端再次訪問相同頁面時,控制器會先檢測是否有對應的db文件,若無則發送請求給服務器,并將返回的數據緩存到SQLite中[12],若有則直接從db文件中取得數據并顯示在界面上。
在請求社區金融新聞信息時,可能同時存在內存緩存和文件緩存,使用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"];
//緩存數組
NSMutableArray *array=@[].mutableCopy;
For (NSInteger i=0;i<10;i++) {
[array addObject:model];
}
//異步緩存
[cache setObject:array forKey:@"user" withBlock:^{
// 異步回調
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){
//異步回調
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",object);}];});

圖5 獲取緩存流程
內存緩存和文件緩存的區別在于,當應用還在進程中時,應用有內存緩存和文件緩存兩種緩存方式,當應用進程被殺死時,應用只存在文件緩存,內存緩存將被清空。因此對于圖片、音頻、視頻等大容量文件,App使用了文件緩存技術將數據緩存到SQLite數據庫中,節省資源;對于文字等小容量內容則緩存到內存中,方便獲取[13]。
為了更好地適應移動互聯網時代,社區金融IOS端App將不斷更新維護,未來開發的重點在于功能模塊的擴展,讓App滿足更多理財需求??紤]到移動開發成本問題,統一IOS和安卓App的跨平臺開發[14]是大勢所趨,因此從Native App到Hybrid App的遷移是以后工作的重點。