標籤:
上一個項目使用到了ReactiveCocoa+MVVM+AFNetworking+FMDB架構設計,從最初的嘗試,到後來不斷思考和學習,現在對這樣一個整體設計還是有了一定了理解與心得。在此與大家分享下。
本文將不再過多的描述ReactiveCocoa、MVVM、FMDB的使用細節。關於ReactiveCocoa,我有一篇實用案例的部落格:
http://www.brighttj.com/ios/ios-reactivecocoa-utility-demo.html
文章介紹的更多的是我對這個架構設計的理解,而不是具體代碼邏輯的講解。關於代碼邏輯,我會在Demo中給出詳細的注釋,本文Demo:
https://github.com/saitjr/ReactiveCocoa-MVVM-AFNetworking-FMDB.git
環境資訊:
Mac OS X 10.11
Xcode 7.0.1
iOS 9.0.1
ReactiveCocoa 2.4.7
AFNetworking 2.6.1
FMDB 2.5
MJExtension 2.5.14
本文
工程目錄
先來談談工程目錄吧,
工程目錄
1. 【Module】+【Model】
在這個目錄中,比較核心的是【Module】與【Model】,他們組成了整個MVVM架構。
【Module】與【Model】均包含【Base】,其中有BaseModel、BaseViewModel、BaseViewController。在開發中,我還是習慣無論是否需要基類,都去寫一個。難免開發之初就考慮到,也難免之後需求會變更。
2. 【Interface】介面
這是借鑒了Java中的介面思想,目的是為了統一方法名。例如裡面的SQLInterface.h檔案,就是一個對資料進行CRUD操作的protocol,並且可以規定裡面的方法是否必須實現。
3. 【Configuration】配置
對項目的一些基本配置,如基本宏定義、常量、通知名,亦或是Cell的identifier。宏定義中一般包含項目基本屬性,如:主色調、常用方法等。
在提供的Demo中,我將SQL語句放在了SQL.h中,是因為SQL只有一個檔案在引用,其中的定義方式是:
static NSString * const selectArticleSQL = @"SELECT * FROM article";
而NotificationNames.h會在大部分檔案中用到,所以使用UIKIT_EXTERN定義為了全域變數:
-----.hUIKIT_EXTERN NSString * const LoadAllNotification;-----.mNSString * const LoadAllNotification = @"LoadAllNotification";
5. 【Category】類目
項目中沒有打包的類目,例如給三方或系統類別新增的一些方法。
RAC+MVVM
RAC+MVVM在【Module】和【Model】這兩個目錄中進行實現。在這之中,MVVM是架構思想,RAC只是輔助而已。
一、MVVM
之所以採用MVVM,而不是MVC,也得益於MVVM的一大特點,就是減輕C層的負擔,畢竟以前的C層完全就是百寶箱,什麼樣的代碼都寫在裡面。
對於MVVM我的理解和拆分是這樣的:
1. Model與View
這一層和MVC中的Model、View含義相同。
2. ViewModel
這一層主要作用是將以前寫在ViewController中的資料處理放在ViewModel中,如:網路請求、資料緩衝、無法直接展示的資料處理(如NSNumber這類的,就在VM中處理成NSString,然後V層直接用,而不是在V層中處理)。
二、Demo中VC的設計
圖形結合源碼應該能看個大概。
VC設計
三、自訂cell的設計(可延伸至自訂View的設計)
因為介紹的是VC,所以這裡再單獨說一下HomePageCell這個自訂cell的設計。
因為想到實際項目中,可能會有比較複雜的cell,所以Demo中寫的是一個比較完整的設計方式(如果單單看這個Demo的話,這個自訂cell太簡單,沒必要有一個單獨的VM,有點過度設計)。
cell中的構思是,cell有一個CellVM來管理cell中要顯示的資料,CellVM來自於VC中,dataSource數組。處理方式具體是:在網路請求完成以後,將字典->模型,然後通過模型,初始化CellVM,然後將CellVM放入dataSource數組。
Cell實現部分代碼如下:
@implementation HomePageCell- (void)awakeFromNib { [super awakeFromNib]; [self setupSignal];}- (void)setupSignal { @weakify(self); [RACObserve(self, viewModel) subscribeNext:^(HomePageCellViewModel *viewModel) { @strongify(self); self.textLabel.text = viewModel.titleText; self.detailTextLabel.text = viewModel.authorText; }];}@end
AFNetworking
前幾天,AFNetworking升級到了3.0。將以前基於NSURLConnection的API,全都改成了NSURLSession,關於更新的詳情,可以看AFNetworking 3.0遷移指南這篇文章。
在頁面銷毀、重新請求等情況下,需要將還在隊列中的請求取消,以免佔用資源。
考慮到這一點,結合我的網路請求在VM發送,權衡再三,在BaseViewModel的基礎上,再進行了一次封裝——RequestViewModel。這個VM有AFHTTPSessionManager類的屬性,一個該屬性的懶載入和一個在dealloc中取消要求方法。
在以前使用MVC的時候,我會對AFNetworking進行再次的封裝,這樣更像是一個MVCS的設計,目的是防止VC過重,現在把這部分代碼扔在了VM中,看起來還好,所以並沒有對AFNetworking再次封裝。關於以前的設計方式,可以看這篇文章:
網路請求架構封裝
FMDB
FMDB提供了一種安全執行緒的模式,在這之中維護這一個串列隊列。
1. 初始化
初始化方式參考了開源項目MVVMReactiveCocoa,作者採用了類目的形式給了一個單例:
@implementation FMDatabaseQueue (Extension)+ (instancetype)shareInstense { static FMDatabaseQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = [FMDatabaseQueue databaseQueueWithPath:DB_PATH]; }); return queue;}@end
2. 統一介面
關於CRUD方法的定義,借鑒了Java Interface的設計:
3. SQL語句
根據實際情況,SQL語句參數可以採用?的形式,也可以採用:keyword的形式。
INSERT INTO myTable VALUES (?, ?, ?)INSERT INTO myTable VALUES (:id, :name, :value)
關於資料庫的建立與更新,我並不建議在Bundle中包含xxx.sql這樣的檔案,因為他們會在編譯後一起打包,使用者下載解壓就能看到,並不怎麼安全。目前我是直接在程式中寫的SQL,不知道大家有沒有更好的方式。
4. CRUD
在網路請求成功的時候,存入資料。儲存是一個批量的操作,建議採用事務inTransaction實現。簡單的操作採用inDatabase即可。
FMDatabaseQueue是一個串列隊列,並且inTransaction和inDatabase都是同步線程,需要注意的是不要在block中執行另一個資料庫訪問操作,防止線程死結。
最後
每個項目完後,都會有很多收穫,有很多東西需要整理總結。寫這篇部落格的原因有兩個:
原因之一:因為我在開發過程中踩了不少坑,可能開發到中途,發現架構設計不好。架構如何設計,並沒有一個標準答案,而且設計思想,還需要在不斷實踐中得出,所以每次總結,是為了給自己看,也是為了幫到其他有同樣困擾的朋友。
原因之二:也正是因為我不知道架構到底怎麼樣,所以寫出來,讓大家看看,都多多提出建議。謝謝。
參考
1. 雷純峰的開源項目MVVMReactiveCocoa,這個項目給我的啟發很大,在此謝謝作者,也為開源點贊
2. iOS大型項目開發漫談
3. iOS應用架構談 網路設計方面
【iOS】小項目架構設計(ReactiveCocoa+MVVM+AFNetworking+FMDB)