談談 MVX 中的 Model
- 談談 MVX 中的 Model
- 談談 MVX 中的 View
- 談談 MVX 中的 Controller
- 淺談 MVC、MVP 和 MVVM 架構模式
Follow GitHub: Draveness
常見的 Model 層
在大多數 iOS 的項目中,Model 層只是一個單純的數據結構,你可以看到的絕大部分模型都是這樣的:
struct User {n enum Gender: String {n case male = "male"n case female = "female"n }n let name: Stringn let email: Stringn let age: Intn let gender: Gendern}n
模型起到了定義一堆『坑』的作用,只是一個簡單的模板,並沒有參與到實際的業務邏輯,只是在模型層進行了一層抽象,將服務端發回的 JSON 或者說 Dictionary 對象中的欄位一一取出並裝填到預先定義好的模型中。
我們可以將這種模型層中提供的對象理解為『即開即用』的 Dictionary 實例;在使用時,可以直接從模型中取出屬性,省去了從 Dictionary 中抽出屬性以及驗證是否合法的過程。
let user = User...nnnameLabel.text = user.namenemailLabel.text = user.emailnageLabel.text = "(user.age)"ngenderLabel.text = user.gender.rawValuen
JSON -> Model
使用 Swift 將 Dictionary 轉換成模型,在筆者看來其實是一件比較麻煩的事情,主要原因是 Swift 作為一個號稱類型安全的語言,有著使用體驗非常差的 Optional 特性,從 Dictionary 中取出的值都是不一定存在的,所以如果需要純手寫這個過程其實還是比較麻煩的。
extension User {n init(json: [String: Any]) {n let name = json["name"] as! Stringn let email = json["email"] as! Stringn let age = json["age"] as! Intn let gender = Gender(rawValue: json["gender"] as! String)!n self.init(name: name, email: email, age: age, gender: gender)n }n}n
這裡為 User 模型創建了一個 extension 並寫了一個簡單的模型轉換的初始化方法,當我們從 JSON 對象中取值時,得到的都是 Optional 對象;而在大多數情況下,我們都沒有辦法直接對 Optional 對象進行操作,這就非常麻煩了。
麻煩的 Optional
在 Swift 中遇到無法立即使用的 Optional 對象時,我們可以會使用 ! 默認將字典中取出的值當作非 Optional 處理,但是如果服務端發回的數據為空,這裡就會直接崩潰;當然,也可使用更加安全的 if let 對 Optional 對象進行解包(unwrap)。
extension User {n init?(json: [String: Any]) {n if let name = json["name"] as? String,n let email = json["email"] as? String,n let age = json["age"] as? Int,n let genderString = json["gender"] as? String,n let gender = Gender(rawValue: genderString) {n self.init(name: name, email: email, age: age, gender: gender)n }n return niln }n}n
上面的代碼看起來非常的醜陋,而正是因為上面的情況在 Swift 中非常常見,所以社區在 Swift 2.0 中引入了 guard 關鍵字來優化代碼的結構。
extension User {n init?(json: [String: Any]) {n guard let name = json["name"] as? String,n let email = json["email"] as? String,n let age = json["age"] as? Int,n let genderString = json["gender"] as? String,n let gender = Gender(rawValue: genderString) else {n return niln }n self.init(name: name, email: email, age: age, gender: gender)n }n}n
不過,上面的代碼在筆者看來,並沒有什麼本質的區別,不過使用 guard 對錯誤的情況進行提前返回確實是一個非常好的編程習慣。
不關心空值的 OC
為什麼 Objective-C 中沒有這種問題呢?主要原因是在 OC 中所有的對象其實都是 Optional 的,我們也並不在乎對象是否為空,因為在 OC 中向 nil 對象發送消息並不會造成崩潰,Objective-C 運行時仍然會返回 nil 對象。
這雖然在一些情況下會造成一些問題,比如,當 nil 導致程序發生崩潰時,比較難找到程序中 nil 出現的原始位置,但是卻保證了程序的靈活性,筆者更傾向於 Objective-C 中的做法,不過這也就見仁見智了。
OC 作為動態語言,這種設計思路其實還是非常優秀的,它避免了大量由於對象不存在導致無法完成方法調用造成的崩潰;同時,作為開發者,我們往往都不需要考慮 nil 的存在,所以使用 OC 時寫出的模型轉換的代碼都相對好看很多。
// User.hntypedef NS_ENUM(NSUInteger, Gender) {n Male = 0,n Female = 1,n};nn@interface User: NSObjectnn@property (nonatomic, strong) NSString *email;n@property (nonatomic, strong) NSString *name;n@property (nonatomic, assign) NSUInteger age;n@property (nonatomic, assign) Gender gender;nn@endnn// User.mn@implementation Usernn- (instancetype)initWithJSON:(NSDictionary *)json {n if (self = [super init]) {n self.email = json[@"email"];n self.name = json[@"name"];n self.age = [json[@"age"] integerValue];n self.gender = [json[@"gender"] integerValue];n }n return self;n}nn@endn
當然,在 OC 中也有很多優秀的 JSON 轉模型的框架,如果我們使用 YYModel 這種開源框架,其實只需要寫一個 User 類的定義就可以獲得 -yy_modelWithJSON: 等方法來初始化 User 對象:
User *user = [User yy_modelWithJSON:json];n
而這也是通過 Objective-C 強大的運行時特性做到的。
除了 YYModel,我們也可以使用 Mantle 等框架在 OC 中解決 JSON 到模型的轉換的問題。
元編程能力
從上面的代碼,我們可以看出:Objective-C 和 Swift 對於相同功能的處理,卻有較大差別的實現。這種情況的出現主要原因是語言的設計思路導致的;Swift 一直鼓吹自己有著較強的安全性,能夠寫出更加穩定可靠的應用程序,而安全性來自於 Swift 語言的設計哲學;由此看來靜態類型、安全和動態類型、元編程能力(?)看起來是比較難以共存的。
其實很多靜態編程語言,比如 C、C++ 和 Rust 都通過宏實現了比較強大的元編程能力,雖然 Swift 也通過模板在元編程支持上做了一些微小的努力,不過到目前來看( 3.0 )還是遠遠不夠的。
OC 中對於 nil 的處理能夠減少我們在編碼時的工作量,不過也對工程師的代碼質量提出了考驗。我們需要思考 nil 的出現會不會帶來崩潰,是否會導致行為的異常、增加應用崩潰的風險以及不確定性,而這也是 Swift 引入 Optional 這一概念來避免上述問題的初衷。
相比而言,筆者還是更喜歡強大的元編程能力,這樣可以減少大量的重複工作並且提供更多的可能性,與提升工作效率相比,犧牲一些安全性還是可以接受的。
網路服務 Service 層
現有的大多數應用都會將網路服務組織成單獨的一層,所以有時候你會看到所謂的 MVCS 架構模式,它其實只是在 MVC 的基礎上加上了一個服務層(Service),而在 iOS 中常見的 MVC 架構模式也都可以理解為 MVCS 的形式,當引入了 Service 層之後,整個數據的獲取以及處理的流程是這樣的:
- 大多數情況下服務的發起都是在 Controller 中進行的;
- 然後會在 HTTP 請求的回調中交給模型層處理 JSON 數據;
- 返回開箱即用的對象交還給 Controller 控制器;
- 最後由 View 層展示服務端返回的數據;
不過按理來說服務層並不屬於模型層,為什麼要在這裡進行介紹呢?這是因為 Service 層其實與 Model 層之間的聯繫非常緊密;網路請求返回的結果決定了 Model 層該如何設計以及該有哪些功能模塊,而 Service 層的設計是與後端的 API 介面的設計強關聯的,這也是我們談模型層的設計無法繞過的坑。
iOS 中的 Service 層大體上有兩種常見的組織方式,其中一種是命令式的,另一種是聲明式的。
命令式
命令式的 Service 層一般都會為每一個或者一組 API 寫一個專門用於 HTTP 請求的 Manager 類,在這個類中,我們會在每一個靜態方法中使用 AFNetworking 或者 Alamofire 等網路框架發出 HTTP 請求。
import Foundationnimport Alamofirennfinal class UserManager {n static let baseURL = "http://localhost:3000"n static let usersBaseURL = "(baseURL)/users"nn static func allUsers(completion: @escaping ([User]) -> ()) {n let url = "(usersBaseURL)"n Alamofire.request(url).responseJSON { response inn if let jsons = response.result.value as? [[String: Any]] {n let users = User.users(jsons: jsons)n completion(users)n }n }n }nn static func user(id: Int, completion: @escaping (User) -> ()) {n let url = "(usersBaseURL)/(id)"n Alamofire.request(url).responseJSON { response inn if let json = response.result.value as? [String: Any],n let user = User(json: json) {n completion(user)n }n }n }n}n
在這個方法中,我們完成了網路請求、數據轉換 JSON、JSON 轉換到模型以及最終使用 completion 回調的過程,調用 Service 服務的 Controller 可以直接從回調中使用構建好的 Model 對象。
UserManager.user(id: 1) { user inn self.nameLabel.text = user.namen self.emailLabel.text = user.emailn self.ageLabel.text = "(user.age)"n self.genderLabel.text = user.gender.rawValuen}n
聲明式
使用聲明式的網路服務層與命令式的方法並沒有本質的不同,它們最終都調用了底層的一些網路庫的 API,這種網路服務層中的請求都是以配置的形式實現的,需要對原有的命令式的請求進行一層封裝,也就是說所有的參數 requestURL、method 和 parameters 都應該以配置的形式聲明在每一個 Request 類中。
如果是在 Objective-C 中,一般會定義一個抽象的基類,並讓所有的 Request 都繼承它;但是在 Swift 中,我們可以使用協議以及協議擴展的方式實現這一功能
protocol AbstractRequest {n var requestURL: String { get }n var method: HTTPMethod { get }n var parameters: Parameters? { get }n}nnextension AbstractRequest {n func start(completion: @escaping (Any) -> Void) {n Alamofire.request(requestURL, method: self.method).responseJSON { response inn if let json = response.result.value {n completion(json)n }n }n }n}n
在 AbstractRequest 協議中,我們定義了發出一個請求所需要的全部參數,並在協議擴展中實現了 start(completion:) 方法,這樣實現該協議的類都可以直接調用 start(completion:) 發出網路請求。
final class AllUsersRequest: AbstractRequest {n let requestURL = "http://localhost:3000/users"n let method = HTTPMethod.getn let parameters: Parameters? = niln}nnfinal class FindUserRequest: AbstractRequest {n let requestURL: Stringn let method = HTTPMethod.getn let parameters: Parameters? = nilnn init(id: Int) {n self.requestURL = "http://localhost:3000/users/(id)"n }n}n
我們在這裡寫了兩個簡單的 Request 類 AllUsersRequest 和 FindUserRequest,它們兩個一個負責獲取所有的 User 對象,一個負責從服務端獲取指定的 User;在使用上面的聲明式 Service 層時也與命令式有一些不同:
FindUserRequest(id: 1).start { json inn if let json = json as? [String: Any],n let user = User(json: json) {n print(user)n }n}n
因為在 Swift 中,我們沒法將 JSON 在 Service 層轉換成模型對象,所以我們不得不在 FindUserRequest 的回調中進行類型以及 JSON 轉模型等過程;又因為 HTTP 請求可能依賴其他的參數,所以在使用這種形式請求資源時,我們需要在初始化方法傳入參數。
命令式 vs 聲明式
現有的 iOS 開發中的網路服務層一般都是使用這兩種組織方式,我們一般會按照資源或者功能來劃分命令式中的 Manager 類,而聲明式的 Request 類與實際請求是一對一的關係。
這兩種網路層的組織方法在筆者看來沒有高下之分,無論是 Manager 還是 Request 的方式,尤其是後者由於一個類只對應一個 API 請求,在整個 iOS 項目變得異常複雜時,就會導致網路層類的數量劇增。
這個問題並不是不可以接受的,在大多數項目中的網路請求就是這麼做的,雖然在查找實際的請求類時有一些麻煩,不過只要遵循一定的命名規範還是可以解決的。
小結
現有的 MVC 下的 Model 層,其實只起到了對數據結構定義的作用,它將服務端返回的 JSON 數據,以更方便使用的方式包裝了一下,這樣呈現給上層的就是一些即拆即用的『字典』。
單獨的 Model 層並不能返回什麼關鍵的作用,它只有與網路服務層 Service 結合在一起的時候才能發揮更重要的能力。
而網路服務 Service 層是對 HTTP 請求的封裝,其實現形式有兩種,一種是命令式的,另一種是聲明式的,這兩種實現的方法並沒有絕對的優劣,遵循合適的形式設計或者重構現有的架構,隨著應用的開發與迭代,為上層提供相同的介面,保持一致性才是設計 Service 層最重要的事情。
服務端的 Model 層
雖然文章是對客戶端中 Model 層進行分析和介紹,但是在客戶端大規模使用 MVC 架構模式之前,服務端對於 MVC 的使用早已有多年的歷史,而移動端以及 Web 前端對於架構的設計是近年來才逐漸被重視。
因為客戶端的應用變得越來越複雜,動輒上百萬行代碼的巨型應用不斷出現,以前流水線式的開發已經沒有辦法解決現在的開發、維護工作,所以合理的架構設計成為客戶端應用必須要重視的事情。
這一節會以 Ruby on Rails 中 Model 層的設計為例,分析在經典的 MVC 框架中的 Model 層是如何與其他模塊進行交互的,同時它又擔任了什麼樣的職責。
Model 層的職責
Rails 中的 Model 層主要承擔著以下兩大職責:
- 使用資料庫存儲並管理 Web 應用的數據;
- 包含 Web 應用所有的業務邏輯;
除了上述兩大職責之外,Model 層還會存儲應用的狀態,同時,由於它對用戶界面一無所知,所以它不依賴於任何視圖的狀態,這也使得 Model 層的代碼可以復用。
Model 層的兩大職責決定了它在整個 MVC 框架的位置:
因為 Model 是對資料庫中表的映射,所以當 Controller 向 Model 層請求數據時,它會從資料庫中獲取相應的數據,然後對數據進行加工最後返回給 Controller 層。
資料庫
Model 層作為資料庫中表的映射,它就需要實現兩部分功能:
- 使用合理的方式對資料庫進行遷移和更新;
- 具有資料庫的絕大部分功能,包括最基礎的增刪改查;
在這裡我們以 Rails 的 ActiveRecord 為例,簡單介紹這兩大功能是如何工作的。
ActiveRecord 為資料庫的遷移和更新提供了一種名為 Migration 的機制,它可以被理解為一種 DSL,對資料庫中的表的欄位、類型以及約束進行描述:
class CreateProducts < ActiveRecord::Migration[5.0]n def changen create_table :products do |t|n t.string :namen t.text :descriptionn endn endnendn
上面的 Ruby 代碼創建了一個名為 Products 表,其中包含三個欄位 name、description 以及一個默認的主鍵 id,然而在上述文件生成時,資料庫中對應的表還不存在,當我們在命令行中執行 rake db:migrate 時,才會執行下面的 SQL 語句生成一張表:
CREATE TABLE products (n id int(11) DEFAULT NULL auto_increment PRIMARY KEYn name VARCHAR(255),n description text,n);n
同樣地,如果我們想要更新資料庫中的表的欄位,也需要創建一個 Migration 文件,ActiveRecord 會為我們直接生成一個 SQL 語句並在資料庫中執行。
ActiveRecord 對資料庫的增刪改查功能都做了相應的實現,在使用它進行資料庫查詢時,會生成一條 SQL 語句,在資料庫中執行,並將執行的結果初始化成一個 Model 的實例並返回:
user = User.find(10)n# => SELECT * FROM users WHERE (users.id = 10) LIMIT 1n
這就是 ActiveRecord 作為 Model 層的 ORM 框架解決兩個關鍵問題的方式,其最終結果都是生成一條 SQL 語句並扔到資料庫中執行。
總而言之,Model 層為調用方屏蔽了所有與資料庫相關的底層細節,使開發者不需要考慮如何手寫 SQL 語句,只需要關心原生的代碼,能夠極大的降低出錯的概率;但是,由於 SQL 語句都由 Model 層負責處理生成,它並不會根據業務幫助我們優化 SQL 查詢語句,所以在遇到數據量較大時,其性能難免遇到各種問題,我們仍然需要手動優化查詢的 SQL 語句。
Controller
Model 與資料庫之間的關係其實大多數都與數據的存儲查詢有關,而與 Controller 的關係就不是這樣了,在 Rails 這個 MVC 框架中,提倡將業務邏輯放到 Model 層進行處理,也就是所謂的:
Fat Models, skinny controllers.
這種說法形成的原因是,在絕大部分的 MVC 框架中,Controller 的作用都是將請求代理給 Model 去完成,它本身並不包含任何的業務邏輯,任何實際的查詢、更新和刪除操作都不應該在 Controller 層直接進行,而是要講這些操作交給 Model 去完成。
class UsersControllern def shown @user = User.find params[:id]n endnendn
這也就是為什麼在後端應用中設計合理的 Controller 實際上並沒有多少行代碼,因為大多數業務邏輯相關的代碼都會放到 Model 層。
Controller 的作用更像是膠水,將 Model 層中獲取的模型傳入 View 層中,渲染 HTML 或者返回 JSON 數據。
小結
雖然服務端對於應用架構的設計已經有了很長時間的沉澱,但是由於客戶端和服務端的職責截然不同,我們可以從服務端借鑒一些設計,但是並不應該照搬後端應用架構設計的思路。
服務端重數據,如果把整個 Web 應用看做一個黑箱,那麼它的輸入就是用戶發送的數據,發送的形式無論是遵循 HTTP 協議也好還是其它協議也好,它們都是數據。
在服務端拿到數據後對其進行處理、加工以及存儲,最後仍然以數據的形式返回給用戶。
而客戶端重展示,其輸入就是用戶的行為觸發的事件,而輸出是用戶界面:
也就是說,用戶的行為在客戶端應用中得到響應,並更新了用戶界面 GUI。總而言之:
客戶端重展示,服務端重數據。
這也是在設計客戶端 Model 層時需要考慮的重要因素。
理想中的 Model 層
在上面的兩個小節中,分別介紹了 iOS 中現有的 Model 層以及服務端的 Model 層是如何使用的,並且介紹了它們的職責,在這一章節中,我們準備介紹筆者對於 Model 層的看法以及設計。
明確職責
在具體討論 Model 層設計之前,肯定要明確它的職責,它應該做什麼、不應該做什麼以及需要為外界提供什麼樣的介面和功能。
客戶端重展示,無論是 Web、iOS 還是 Android,普通用戶應該無法直接接觸到服務端,如果一個軟體系統的使用非常複雜,並且讓普通用戶直接接觸到服務端的各種報錯、提示,比如 404 等等,那麼這個軟體的設計可能就是不合理的。
這裡加粗了普通和直接兩個詞,如果對這句話有疑問,請多讀幾遍 :)n專業的錯誤信息在軟體工程師介入排錯時非常有幫助,這種信息應當放置在不明顯的角落。
作為軟體工程師或者設計師,應該為用戶提供更加合理的界面以及展示效果,比如,使用您所瀏覽的網頁不存在來描述或者代替只有從事軟體開發行業的人才了解的 404 或者 500 等錯誤是更為合適的方式。
上面的例子主要是為了說明客戶端的最重要的職責,將數據合理地展示給用戶,從這裡我們可以領會到,Model 層雖然重要,但是卻不是客戶端最為複雜的地方,它只是起到了一個將服務端數據『映射』到客戶端的作用,這個映射的過程就是獲取數據的過程,也決定了 Model 層在 iOS 應用中的位置。
那麼這樣就產生了幾個非常重要的問題和子問題:
- 數據如何獲取?
- 在何時獲取數據?
- 如何存儲服務端的數據?
- 數據如何展示?
- 應該為上層提供什麼樣的介面?
Model 層 += Service 層?
首先,我們來解決數據獲取的問題,在 iOS 客戶端常見的 Model 層中,數據的獲取都不是由 Model 層負責的,而是由一個單獨的 Service 層進行處理,然而經常這麼組織網路請求並不是一個非常優雅的辦法:
- 如果按照 API 組織 Service 層,那麼網路請求越多,整個項目的 Service 層的類的數量就會越龐大;
- 如果按照資源組織 Service 層,那麼為什麼不把 Service 層中的代碼直接扔到 Model 層呢?
既然 HTTP 請求都以獲取相應的資源為目標,那麼以 Model 層為中心來組織 Service 層並沒有任何語義和理解上的問題。
如果服務端的 API 嚴格地按照 RESTful 的形式進行設計,那麼就可以在客戶端的 Model 層建立起一一對應的關係,拿最基本的幾個 API 請求為例:
extension RESTful {n static func index(completion: @escaping ([Self]) -> ())nn static func show(id: Int, completion: @escaping (Self?) -> ())nn static func create(params: [String: Any], completion: @escaping (Self?) -> ()) nn static func update(id: Int, params: [String: Any], completion: @escaping (Self?) -> ())nn static func delete(id: Int, completion: @escaping () -> ())n}n
我們在 Swift 中通過 Protocol Extension 的方式為所有遵循 RESTful 協議的模型添加基本的 CRUD 方法,那麼 RESTful 協議本身又應該包含什麼呢?
protocol RESTful {n init?(json: [String: Any])n static var url: String { get }n}n
RESTful 協議本身也十分簡單,一是 JSON 轉換方法,也就是如何將伺服器返回的 JSON 數據轉換成對應的模型,另一個是資源的 url
對於這裡的 url,我們可以遵循約定優於配置的原則,通過反射獲取一個默認的資源鏈接,從而簡化原有的 RESTful 協議,但是這裡為了簡化代碼並沒有使用這種方法。
extension User: RESTful {n static var url: String {n return "http://localhost:3000/users"n }nn init?(json: [String: Any]) {n guard let id = json["id"] as? Int,n let name = json["name"] as? String,n let email = json["email"] as? String,n let age = json["age"] as? Int,n let genderValue = json["gender"] as? Int,n let gender = Gender(rawInt: genderValue) else {n return niln }n self.init(id: id, name: name, email: email, age: age, gender: gender)n }n}n
在 User 模型遵循上述協議之後,我們就可以簡單的通過它的靜態方法來對伺服器上的資源進行一系列的操作。
User.index { users inn // usersn}nnUser.create(params: ["name": "Stark", "email": "example@email.com", "gender": 0, "age": 100]) { user inn // usern}n
當然 RESTful 的 API 介面仍然需要服務端提供支持,不過以 Model 取代 Service 作為 HTTP 請求的發出者確實是可行的。
問題
雖然上述的方法簡化了 Service 層,但是在真正使用時確實會遇到較多的限制,比如,用戶需要對另一用戶進行關注或者取消關注操作,這樣的 API 如果要遵循 RESTful 就需要使用以下的方式進行設計:
POST /api/users/1/followsnDELETE /api/users/1/followsn
這種情況就會導致在當前的客戶端的 Model 層沒法建立合適的抽象,因為 follows 並不是一個真實存在的模型,它只代表兩個用戶之間的關係,所以在當前所設計的模型層中沒有辦法實現上述的功能,還需要引入 Service 層,來對服務端中的每一個 Controller 的 action 進行抽象,在這裡就不展開討論了。
對 Model 層網路服務的設計,與服務端的設計有著非常大的關聯,如果能夠對客戶端和服務端之間的 API 進行嚴格規範,那麼對於設計出簡潔、優雅的網路層還是有巨大幫助的。
緩存與持久存儲
客戶端的持久存儲其實與服務端的存儲天差地別,客戶端中保存的各種數據更準確的說其實是緩存,既然是緩存,那麼它在客戶端應用中的地位並不是極其重要、非他不可的;正相反,很多客戶端應用沒有緩存也運行的非常好,它並不是一個必要的功能,只是能夠提升用戶體驗而已。
雖然客戶端的存儲只是緩存,但是在目前的大型應用中,也確實需要這種緩存,有以下幾個原因:
- 能夠快速為用戶提供可供瀏覽的內容;
- 在網路情況較差或者無網路時,也能夠為用戶提供兜底數據;
以上的好處其實都是從用戶體驗的角度說的,不過緩存確實能夠提高應用的質量。
在 iOS 中,持久存儲雖然不是一個必要的功能,但是蘋果依然為我們提供了不是那麼好用的 Core Data 框架,但這並不是這篇文章需要介紹和討論的內容。
目前的絕大多數 Model 框架,其實提供的都只是硬編碼的資料庫操作能力,或者提供的 API 不夠優雅,原因是雖然 Swift 語法比 Objective-C 更加簡潔,但是缺少元編程能力是它的硬傷。
熟悉 ActiveRecord 的開發者應該都熟悉下面的使用方式:
User.find_by_name "draven"n
在 Swift 中通過現有的特性很難提供這種 API,所以很多情況下只能退而求其次,繼承 NSObject 並且使用 dynamic 關鍵字記住 Objective-C 的特性實現一些功能:
class User: Object {n dynamic var name = ""n dynamic var age = 0n}n
這確實是一種解決辦法,但是並不是特別的優雅,如果我們在編譯器間獲得模型信息,然後使用這些信息生成代碼就可以解決這些問題了,這種方法同時也能夠在 Xcode 編譯器中添加代碼提示。
上層介面
Model 層為上層提供提供的介面其實就是自身的一系列屬性,只是將伺服器返回的 JSON 經過處理和類型轉換,變成了即拆即用的數據。
上層與 Model 層交互有兩種方式,一是通過 Model 層調用 HTTP 請求,非同步獲取模型數據,另一種就是通過 Model 暴露出來的屬性進行存取,而底層資料庫會在 Model 屬性更改時發出網路請求並且修改對應的欄位。
總結
雖然客戶端的 Model 層與服務端的 Model 層有著相同的名字,但是客戶端的 Model 層由於處理的是緩存,對本地的資料庫中的表進行遷移、更改並不是一個必要的功能,在本地表欄位進行大規模修改時,只需要刪除全部表中的內容,並重新創建即可,只要不影響服務端的數據就不是太大的問題。
iOS 中的 Model 層不應該是一個單純的數據結構,它應該起到發出 HTTP 請求、進行欄位驗證以及持久存儲的職責,同時為上層提供網路請求的方法以及欄位作為介面,為視圖的展示提供數據源的作用。我們應該將更多的與 Model 層有關的業務邏輯移到 Model 中以控制 Controller 的複雜性。
推薦閱讀:
※是「榴槤」而不是「榴槤」
※iOSApp間常用的五種通信方式
※有哪些第三方工具可分析得到 iOS App 的運營數據?
※從 NSObject 的初始化了解 isa