談談 MVX 中的 Model

Follow GitHub: Dravenesshtml

常見的 Model 層

在大多數 iOS 的項目中,Model 層只是一個單純的數據結構,你能夠看到的絕大部分模型都是這樣的:前端

struct User {
    enum Gender: String {
        case male = "male"
        case female = "female"
    }
    let name: String
    let email: String
    let age: Int
    let gender: Gender
}複製代碼

模型起到了定義一堆『坑』的做用,只是一個簡單的模板,並無參與到實際的業務邏輯,只是在模型層進行了一層抽象,將服務端發回的 JSON 或者說 Dictionary 對象中的字段一一取出並裝填到預先定義好的模型中。git

JSON-to-Mode
JSON-to-Mode

咱們能夠將這種模型層中提供的對象理解爲『即開即用』的 Dictionary 實例;在使用時,能夠直接從模型中取出屬性,省去了從 Dictionary 中抽出屬性以及驗證是否合法的過程。github

let user = User...

nameLabel.text = user.name
emailLabel.text = user.email
ageLabel.text = "\(user.age)"
genderLabel.text = user.gender.rawValue複製代碼

JSON -> Model

使用 Swift 將 Dictionary 轉換成模型,在筆者看來實際上是一件比較麻煩的事情,主要緣由是 Swift 做爲一個號稱類型安全的語言,有着使用體驗很是差的 Optional 特性,從 Dictionary 中取出的值都是不必定存在的,因此若是須要純手寫這個過程其實仍是比較麻煩的。web

extension User {
    init(json: [String: Any]) {
        let name = json["name"] as! String
        let email = json["email"] as! String
        let age = json["age"] as! Int
        let gender = Gender(rawValue: json["gender"] as! String)!
        self.init(name: name, email: email, age: age, gender: gender)
    }
}複製代碼

這裏爲 User 模型建立了一個 extension 並寫了一個簡單的模型轉換的初始化方法,當咱們從 JSON 對象中取值時,獲得的都是 Optional 對象;而在大多數狀況下,咱們都沒有辦法直接對 Optional 對象進行操做,這就很是麻煩了。sql

麻煩的 Optional

在 Swift 中遇到沒法當即使用的 Optional 對象時,咱們能夠會使用 ! 默認將字典中取出的值看成非 Optional 處理,可是若是服務端發回的數據爲空,這裏就會直接崩潰;固然,也可以使用更加安全的 if let 對 Optional 對象進行解包(unwrap)。數據庫

extension User {
    init?(json: [String: Any]) {
        if let name = json["name"] as? String,
            let email = json["email"] as? String,
            let age = json["age"] as? Int,
            let genderString = json["gender"] as? String,
            let gender = Gender(rawValue: genderString) {
            self.init(name: name, email: email, age: age, gender: gender)
        }
        return nil
    }
}複製代碼

上面的代碼看起來很是的醜陋,而正是由於上面的狀況在 Swift 中很是常見,因此社區在 Swift 2.0 中引入了 guard 關鍵字來優化代碼的結構。編程

extension User {
    init?(json: [String: Any]) {
        guard let name = json["name"] as? String,
            let email = json["email"] as? String,
            let age = json["age"] as? Int,
            let genderString = json["gender"] as? String,
            let gender = Gender(rawValue: genderString) else {
                return nil
        }
        self.init(name: name, email: email, age: age, gender: gender)
    }
}複製代碼

不過,上面的代碼在筆者看來,並無什麼本質的區別,不過使用 guard 對錯誤的狀況進行提早返回確實是一個很是好的編程習慣。json

不關心空值的 OC

爲何 Objective-C 中沒有這種問題呢?主要緣由是在 OC 中全部的對象其實都是 Optional 的,咱們也並不在意對象是否爲空,由於在 OC 中向 nil 對象發送消息並不會形成崩潰,Objective-C 運行時仍然會返回 nil 對象swift

這雖然在一些狀況下會形成一些問題,好比,當 nil 致使程序發生崩潰時,比較難找到程序中 nil 出現的原始位置,可是卻保證了程序的靈活性,筆者更傾向於 Objective-C 中的作法,不過這也就見仁見智了。

OC 做爲動態語言,這種設計思路其實仍是很是優秀的,它避免了大量因爲對象不存在致使沒法完成方法調用形成的崩潰;同時,做爲開發者,咱們每每都不須要考慮 nil 的存在,因此使用 OC 時寫出的模型轉換的代碼都相對好看不少。

// User.h
typedef NS_ENUM(NSUInteger, Gender) {
    Male = 0,
    Female = 1,
};

@interface User: NSObject

@property (nonatomic, strong) NSString *email;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, assign) Gender gender;

@end

// User.m
@implementation User

- (instancetype)initWithJSON:(NSDictionary *)json {
    if (self = [super init]) {
        self.email = json[@"email"];
        self.name = json[@"name"];
        self.age = [json[@"age"] integerValue];
        self.gender = [json[@"gender"] integerValue];
    }
    return self;
}

@end複製代碼

固然,在 OC 中也有不少優秀的 JSON 轉模型的框架,若是咱們使用 YYModel 這種開源框架,其實只須要寫一個 User 類的定義就能夠得到 -yy_modelWithJSON: 等方法來初始化 User 對象:

User *user = [User yy_modelWithJSON:json];複製代碼

而這也是經過 Objective-C 強大的運行時特性作到的。

除了 YYModel,咱們也可使用 Mantle 等框架在 OC 中解決 JSON 到模型的轉換的問題。

元編程能力

從上面的代碼,咱們能夠看出:Objective-C 和 Swift 對於相同功能的處理,卻有較大差異的實現。這種狀況的出現主要緣由是語言的設計思路致使的;Swift 一直鼓吹本身有着較強的安全性,可以寫出更加穩定可靠的應用程序,而安全性來自於 Swift 語言的設計哲學;由此看來靜態類型、安全和動態類型、元編程能力(?)看起來是比較難以共存的。

其實不少靜態編程語言,好比 C、C++ 和 Rust 都經過宏實現了比較強大的元編程能力,雖然 Swift 也經過模板在元編程支持上作了一些微小的努力,不過到目前來看( 3.0 )仍是遠遠不夠的。

Dynamic-Stati
Dynamic-Stati

OC 中對於 nil 的處理可以減小咱們在編碼時的工做量,不過也對工程師的代碼質量提出了考驗。咱們須要思考 nil 的出現會不會帶來崩潰,是否會致使行爲的異常、增長應用崩潰的風險以及不肯定性,而這也是 Swift 引入 Optional 這一律念來避免上述問題的初衷。

相比而言,筆者仍是更喜歡強大的元編程能力,這樣能夠減小大量的重複工做而且提供更多的可能性,與提高工做效率相比,犧牲一些安全性仍是能夠接受的。

網絡服務 Service 層

現有的大多數應用都會將網路服務組織成單獨的一層,因此有時候你會看到所謂的 MVCS 架構模式,它其實只是在 MVC 的基礎上加上了一個服務層(Service),而在 iOS 中常見的 MVC 架構模式也均可以理解爲 MVCS 的形式,當引入了 Service 層以後,整個數據的獲取以及處理的流程是這樣的:

MVCS-Architecture
MVCS-Architecture

  1. 大多數狀況下服務的發起都是在 Controller 中進行的;
  2. 而後會在 HTTP 請求的回調中交給模型層處理 JSON 數據;
  3. 返回開箱即用的對象交還給 Controller 控制器;
  4. 最後由 View 層展現服務端返回的數據;

不過按理來講服務層並不屬於模型層,爲何要在這裏進行介紹呢?這是由於 Service 層其實與 Model 層之間的聯繫很是緊密;網絡請求返回的結果決定了 Model 層該如何設計以及該有哪些功能模塊,而 Service 層的設計是與後端的 API 接口的設計強關聯的,這也是咱們談模型層的設計沒法繞過的坑。

iOS 中的 Service 層大致上有兩種常見的組織方式,其中一種是命令式的,另外一種是聲明式的。

命令式

命令式的 Service 層通常都會爲每個或者一組 API 寫一個專門用於 HTTP 請求的 Manager 類,在這個類中,咱們會在每個靜態方法中使用 AFNetworking 或者 Alamofire 等網絡框架發出 HTTP 請求。

import Foundation
import Alamofire

final class UserManager {
    static let baseURL = "http://localhost:3000"
    static let usersBaseURL = "\(baseURL)/users"

    static func allUsers(completion: @escaping ([User]) -> ()) {
        let url = "\(usersBaseURL)"
        Alamofire.request(url).responseJSON { response in
            if let jsons = response.result.value as? [[String: Any]] {
                let users = User.users(jsons: jsons)
                completion(users)
            }
        }
    }

    static func user(id: Int, completion: @escaping (User) -> ()) {
        let url = "\(usersBaseURL)/\(id)"
        Alamofire.request(url).responseJSON { response in
            if let json = response.result.value as? [String: Any],
                let user = User(json: json) {
                completion(user)
            }
        }
    }
}複製代碼

在這個方法中,咱們完成了網絡請求、數據轉換 JSON、JSON 轉換到模型以及最終使用 completion 回調的過程,調用 Service 服務的 Controller 能夠直接從回調中使用構建好的 Model 對象。

UserManager.user(id: 1) { user in
    self.nameLabel.text = user.name
    self.emailLabel.text = user.email
    self.ageLabel.text = "\(user.age)"
    self.genderLabel.text = user.gender.rawValue
}複製代碼

聲明式

使用聲明式的網絡服務層與命令式的方法並無本質的不一樣,它們最終都調用了底層的一些網絡庫的 API,這種網絡服務層中的請求都是以配置的形式實現的,須要對原有的命令式的請求進行一層封裝,也就是說全部的參數 requestURLmethodparameters 都應該以配置的形式聲明在每個 Request 類中。

Abstract-Request
Abstract-Request

若是是在 Objective-C 中,通常會定義一個抽象的基類,並讓全部的 Request 都繼承它;可是在 Swift 中,咱們可使用協議以及協議擴展的方式實現這一功能。

protocol AbstractRequest {
    var requestURL: String { get }
    var method: HTTPMethod { get }
    var parameters: Parameters? { get }
}

extension AbstractRequest {
    func start(completion: @escaping (Any) -> Void) {
        Alamofire.request(requestURL, method: self.method).responseJSON { response in
            if let json = response.result.value {
                completion(json)
            }
        }
    }
}複製代碼

AbstractRequest 協議中,咱們定義了發出一個請求所須要的所有參數,並在協議擴展中實現了 start(completion:) 方法,這樣實現該協議的類均可以直接調用 start(completion:) 發出網絡請求。

final class AllUsersRequest: AbstractRequest {
    let requestURL = "http://localhost:3000/users"
    let method = HTTPMethod.get
    let parameters: Parameters? = nil
}

final class FindUserRequest: AbstractRequest {
    let requestURL: String
    let method = HTTPMethod.get
    let parameters: Parameters? = nil

    init(id: Int) {
        self.requestURL = "http://localhost:3000/users/\(id)"
    }
}複製代碼

咱們在這裏寫了兩個簡單的 RequestAllUsersRequestFindUserRequest,它們兩個一個負責獲取全部的 User 對象,一個負責從服務端獲取指定的 User;在使用上面的聲明式 Service 層時也與命令式有一些不一樣:

FindUserRequest(id: 1).start { json in
    if let json = json as? [String: Any],
        let user = User(json: json) {
        print(user)
    }
}複製代碼

由於在 Swift 中,咱們無法將 JSON 在 Service 層轉換成模型對象,因此咱們不得不在 FindUserRequest 的回調中進行類型以及 JSON 轉模型等過程;又由於 HTTP 請求可能依賴其餘的參數,因此在使用這種形式請求資源時,咱們須要在初始化方法傳入參數。

命令式 vs 聲明式

現有的 iOS 開發中的網絡服務層通常都是使用這兩種組織方式,咱們通常會按照資源或者功能來劃分命令式中的 Manager 類,而聲明式的 Request 類與實際請求是一對一的關係。

Manager-And-Request
Manager-And-Request

這兩種網絡層的組織方法在筆者看來沒有高下之分,不管是 Manager 仍是 Request 的方式,尤爲是後者因爲一個類只對應一個 API 請求,在整個 iOS 項目變得異常複雜時,就會致使網絡層類的數量劇增

這個問題並非不能夠接受的,在大多數項目中的網絡請求就是這麼作的,雖然在查找實際的請求類時有一些麻煩,不過只要遵循必定的命名規範仍是能夠解決的。

小結

現有的 MVC 下的 Model 層,其實只起到了對數據結構定義的做用,它將服務端返回的 JSON 數據,以更方便使用的方式包裝了一下,這樣呈現給上層的就是一些即拆即用的『字典』。

Model-And-Dictioanry
Model-And-Dictioanry

單獨的 Model 層並不能返回什麼關鍵的做用,它只有與網絡服務層 Service 結合在一塊兒的時候才能發揮更重要的能力。

Service-And-API
Service-And-API

而網絡服務 Service 層是對 HTTP 請求的封裝,其實現形式有兩種,一種是命令式的,另外一種是聲明式的,這兩種實現的方法並無絕對的優劣,遵循合適的形式設計或者重構現有的架構,隨着應用的開發與迭代,爲上層提供相同的接口,保持一致性纔是設計 Service 層最重要的事情。

服務端的 Model 層

雖然文章是對客戶端中 Model 層進行分析和介紹,可是在客戶端大規模使用 MVC 架構模式以前,服務端對於 MVC 的使用早已有多年的歷史,而移動端以及 Web 前端對於架構的設計是近年來才逐漸被重視。

由於客戶端的應用變得愈來愈複雜,動輒上百萬行代碼的巨型應用不斷出現,之前流水線式的開發已經沒有辦法解決如今的開發、維護工做,因此合理的架構設計成爲客戶端應用必需要重視的事情。

這一節會以 Ruby on Rails 中 Model 層的設計爲例,分析在經典的 MVC 框架中的 Model 層是如何與其餘模塊進行交互的,同時它又擔任了什麼樣的職責。

Model 層的職責

Rails 中的 Model 層主要承擔着如下兩大職責:

  1. 使用數據庫存儲並管理 Web 應用的數據;
  2. 包含 Web 應用全部的業務邏輯;

除了上述兩大職責以外,Model 層還會存儲應用的狀態,同時,因爲它對用戶界面一無所知,因此它不依賴於任何視圖的狀態,這也使得 Model 層的代碼能夠複用。

Model 層的兩大職責決定了它在整個 MVC 框架的位置:

Server-MV
Server-MV

由於 Model 是對數據庫中表的映射,因此當 Controller 向 Model 層請求數據時,它會從數據庫中獲取相應的數據,而後對數據進行加工最後返回給 Controller 層。

數據庫

Model 層做爲數據庫中表的映射,它就須要實現兩部分功能:

  1. 使用合理的方式對數據庫進行遷移和更新;
  2. 具備數據庫的絕大部分功能,包括最基礎的增刪改查;

在這裏咱們以 Rails 的 ActiveRecord 爲例,簡單介紹這兩大功能是如何工做的。

ActiveRecord 爲數據庫的遷移和更新提供了一種名爲 Migration 的機制,它能夠被理解爲一種 DSL,對數據庫中的表的字段、類型以及約束進行描述:

class CreateProducts < ActiveRecord::Migration[5.0]
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
    end
  end
end複製代碼

上面的 Ruby 代碼建立了一個名爲 Products 表,其中包含三個字段 namedescription 以及一個默認的主鍵 id,然而在上述文件生成時,數據庫中對應的表還不存在,當咱們在命令行中執行 rake db:migrate 時,纔會執行下面的 SQL 語句生成一張表:

CREATE TABLE products (
    id int(11)   DEFAULT NULL auto_increment PRIMARY KEY
    name         VARCHAR(255),
    description  text,
);複製代碼

一樣地,若是咱們想要更新數據庫中的表的字段,也須要建立一個 Migration 文件,ActiveRecord 會爲咱們直接生成一個 SQL 語句並在數據庫中執行。

ActiveRecord 對數據庫的增刪改查功能都作了相應的實現,在使用它進行數據庫查詢時,會生成一條 SQL 語句,在數據庫中執行,並將執行的結果初始化成一個 Model 的實例並返回:

user = User.find(10)
# => SELECT * FROM users WHERE (users.id = 10) LIMIT 1複製代碼

這就是 ActiveRecord 做爲 Model 層的 ORM 框架解決兩個關鍵問題的方式,其最終結果都是生成一條 SQL 語句並扔到數據庫中執行。

Relation-Between-Database-And-Mode
Relation-Between-Database-And-Mode

總而言之,Model 層爲調用方屏蔽了全部與數據庫相關的底層細節,使開發者不須要考慮如何手寫 SQL 語句,只須要關心原生的代碼,可以極大的下降出錯的機率;可是,因爲 SQL 語句都由 Model 層負責處理生成,它並不會根據業務幫助咱們優化 SQL 查詢語句,因此在遇到數據量較大時,其性能不免遇到各類問題,咱們仍然須要手動優化查詢的 SQL 語句。

Controller

Model 與數據庫之間的關係其實大多數都與數據的存儲查詢有關,而與 Controller 的關係就不是這樣了,在 Rails 這個 MVC 框架中,提倡將業務邏輯放到 Model 層進行處理,也就是所謂的:

Fat Models, skinny controllers.

這種說法造成的緣由是,在絕大部分的 MVC 框架中,Controller 的做用都是將請求代理給 Model 去完成,它自己並不包含任何的業務邏輯,任何實際的查詢、更新和刪除操做都不該該在 Controller 層直接進行,而是要講這些操做交給 Model 去完成。

class UsersController
  def show
    @user = User.find params[:id]
  end
end複製代碼

這也就是爲何在後端應用中設計合理的 Controller 實際上並無多少行代碼,由於大多數業務邏輯相關的代碼都會放到 Model 層。

Controller 的做用更像是膠水,將 Model 層中獲取的模型傳入 View 層中,渲染 HTML 或者返回 JSON 數據。

小結

雖然服務端對於應用架構的設計已經有了很長時間的沉澱,可是因爲客戶端和服務端的職責大相徑庭,咱們能夠從服務端借鑑一些設計,可是並不該該照搬後端應用架構設計的思路。

服務端重數據,若是把整個 Web 應用看作一個黑箱,那麼它的輸入就是用戶發送的數據,發送的形式不管是遵循 HTTP 協議也好仍是其它協議也好,它們都是數據。

web-black-box
web-black-box

在服務端拿到數據後對其進行處理、加工以及存儲,最後仍然以數據的形式返回給用戶。

而客戶端重展現,其輸入就是用戶的行爲觸發的事件,而輸出是用戶界面:

client-black-box
client-black-box

也就是說,用戶的行爲在客戶端應用中獲得響應,並更新了用戶界面 GUI。總而言之:

客戶端重展現,服務端重數據。

這也是在設計客戶端 Model 層時須要考慮的重要因素。

理想中的 Model 層

在上面的兩個小節中,分別介紹了 iOS 中現有的 Model 層以及服務端的 Model 層是如何使用的,而且介紹了它們的職責,在這一章節中,咱們準備介紹筆者對於 Model 層的見解以及設計。

明確職責

在具體討論 Model 層設計以前,確定要明確它的職責,它應該作什麼、不該該作什麼以及須要爲外界提供什麼樣的接口和功能。

客戶端重展現,不管是 Web、iOS 仍是 Android,普通用戶應該沒法直接接觸到服務端,若是一個軟件系統的使用很是複雜,而且讓普通用戶直接接觸到服務端的各類報錯、提示,好比 404 等等,那麼這個軟件的設計可能就是不合理的。

這裏加粗了普通和直接兩個詞,若是對這句話有疑問,請多讀幾遍 :)
專業的錯誤信息在軟件工程師介入排錯時很是有幫助,這種信息應當放置在不明顯的角落。

404
404

做爲軟件工程師或者設計師,應該爲用戶提供更加合理的界面以及展現效果,好比,使用您所瀏覽的網頁不存在來描述或者代替只有從事軟件開發行業的人才瞭解的 404 或者 500 等錯誤是更爲合適的方式。

上面的例子主要是爲了說明客戶端的最重要的職責,將數據合理地展現給用戶,從這裏咱們能夠領會到,Model 層雖然重要,可是卻不是客戶端最爲複雜的地方,它只是起到了一個將服務端數據『映射』到客戶端的做用,這個映射的過程就是獲取數據的過程,也決定了 Model 層在 iOS 應用中的位置。

Model-in-Client
Model-in-Client

那麼這樣就產生了幾個很是重要的問題和子問題:

  • 數據如何獲取?
    • 在什麼時候獲取數據?
    • 如何存儲服務端的數據?
  • 數據如何展現?
    • 應該爲上層提供什麼樣的接口?

Model 層 += Service 層?

首先,咱們來解決數據獲取的問題,在 iOS 客戶端常見的 Model 層中,數據的獲取都不是由 Model 層負責的,而是由一個單獨的 Service 層進行處理,然而常常這麼組織網絡請求並非一個很是優雅的辦法:

  1. 若是按照 API 組織 Service 層,那麼網絡請求越多,整個項目的 Service 層的類的數量就會越龐大;
  2. 若是按照資源組織 Service 層,那麼爲何不把 Service 層中的代碼直接扔到 Model 層呢?

既然 HTTP 請求都以獲取相應的資源爲目標,那麼以 Model 層爲中心來組織 Service 層並無任何語義和理解上的問題。

若是服務端的 API 嚴格地按照 RESTful 的形式進行設計,那麼就能夠在客戶端的 Model 層創建起一一對應的關係,拿最基本的幾個 API 請求爲例:

extension RESTful {
    static func index(completion: @escaping ([Self]) -> ())

    static func show(id: Int, completion: @escaping (Self?) -> ())

    static func create(params: [String: Any], completion: @escaping (Self?) -> ()) 

    static func update(id: Int, params: [String: Any], completion: @escaping (Self?) -> ())

    static func delete(id: Int, completion: @escaping () -> ())
}複製代碼

咱們在 Swift 中經過 Protocol Extension 的方式爲全部遵循 RESTful 協議的模型添加基本的 CRUD 方法,那麼 RESTful 協議自己又應該包含什麼呢?

protocol RESTful {
    init?(json: [String: Any])
    static var url: String { get }
}複製代碼

RESTful 協議自己也十分簡單,一是 JSON 轉換方法,也就是如何將服務器返回的 JSON 數據轉換成對應的模型,另外一個是資源的 url

對於這裏的 url,咱們能夠遵循約定優於配置的原則,經過反射獲取一個默認的資源連接,從而簡化原有的 RESTful 協議,可是這裏爲了簡化代碼並無使用這種方法。

extension User: RESTful {
    static var url: String {
        return "http://localhost:3000/users"
    }

    init?(json: [String: Any]) {
        guard let id = json["id"] as? Int,
            let name = json["name"] as? String,
            let email = json["email"] as? String,
            let age = json["age"] as? Int,
            let genderValue = json["gender"] as? Int,
            let gender = Gender(rawInt: genderValue) else {
                return nil
        }
        self.init(id: id, name: name, email: email, age: age, gender: gender)
    }
}複製代碼

User 模型遵循上述協議以後,咱們就能夠簡單的經過它的靜態方法來對服務器上的資源進行一系列的操做。

User.index { users in
    // users
}

User.create(params: ["name": "Stark", "email": "example@email.com", "gender": 0, "age": 100]) { user in
    // user
}複製代碼

固然 RESTful 的 API 接口仍然須要服務端提供支持,不過以 Model 取代 Service 做爲 HTTP 請求的發出者確實是可行的。

問題

雖然上述的方法簡化了 Service 層,可是在真正使用時確實會遇到較多的限制,好比,用戶須要對另外一用戶進行關注或者取消關注操做,這樣的 API 若是要遵循 RESTful 就須要使用如下的方式進行設計:

POST   /api/users/1/follows
DELETE /api/users/1/follows複製代碼

這種狀況就會致使在當前的客戶端的 Model 層無法創建合適的抽象,由於 follows 並非一個真實存在的模型,它只表明兩個用戶之間的關係,因此在當前所設計的模型層中沒有辦法實現上述的功能,還須要引入 Service 層,來對服務端中的每個 Controller 的 action 進行抽象,在這裏就不展開討論了。

對 Model 層網絡服務的設計,與服務端的設計有着很是大的關聯,若是可以對客戶端和服務端之間的 API 進行嚴格規範,那麼對於設計出簡潔、優雅的網絡層仍是有巨大幫助的。

緩存與持久存儲

客戶端的持久存儲其實與服務端的存儲天差地別,客戶端中保存的各類數據更準確的說實際上是緩存,既然是緩存,那麼它在客戶端應用中的地位並非極其重要、非他不可的;正相反,不少客戶端應用沒有緩存也運行的很是好,它並非一個必要的功能,只是可以提高用戶體驗而已。

雖然客戶端的存儲只是緩存,可是在目前的大型應用中,也確實須要這種緩存,有如下幾個緣由:

  • 可以快速爲用戶提供可供瀏覽的內容;
  • 在網絡狀況較差或者無網絡時,也可以爲用戶提供兜底數據;

以上的好處其實都是從用戶體驗的角度說的,不過緩存確實可以提升應用的質量。

在 iOS 中,持久存儲雖然不是一個必要的功能,可是蘋果依然爲咱們提供了不是那麼好用的 Core Data 框架,但這並非這篇文章須要介紹和討論的內容。

目前的絕大多數 Model 框架,其實提供的都只是硬編碼的數據庫操做能力,或者提供的 API 不夠優雅,緣由是雖然 Swift 語法比 Objective-C 更加簡潔,可是缺乏元編程能力是它的硬傷。

熟悉 ActiveRecord 的開發者應該都熟悉下面的使用方式:

User.find_by_name "draven"複製代碼

在 Swift 中經過現有的特性很難提供這種 API,因此不少狀況下只能退而求其次,繼承 NSObject 而且使用 dynamic 關鍵字記住 Objective-C 的特性實現一些功能:

class User: Object {
    dynamic var name = ""
    dynamic var age = 0
}複製代碼

這確實是一種解決辦法,可是並非特別的優雅,若是咱們在編譯器間得到模型信息,而後使用這些信息生成代碼就能夠解決這些問題了,這種方法同時也可以在 Xcode 編譯器中添加代碼提示。

上層接口

Model 層爲上層提供提供的接口其實就是自身的一系列屬性,只是將服務器返回的 JSON 通過處理和類型轉換,變成了即拆即用的數據。

JSON-Mode
JSON-Mode

上層與 Model 層交互有兩種方式,一是經過 Model 層調用 HTTP 請求,異步獲取模型數據,另外一種就是經過 Model 暴露出來的屬性進行存取,而底層數據庫會在 Model 屬性更改時發出網絡請求而且修改對應的字段。

總結

雖然客戶端的 Model 層與服務端的 Model 層有着相同的名字,可是客戶端的 Model 層因爲處理的是緩存,對本地的數據庫中的表進行遷移、更改並非一個必要的功能,在本地表字段進行大規模修改時,只須要刪除所有表中的內容,並從新建立便可,只要不影響服務端的數據就不是太大的問題。

iOS 中的 Model 層不該該是一個單純的數據結構,它應該起到發出 HTTP 請求、進行字段驗證以及持久存儲的職責,同時爲上層提供網絡請求的方法以及字段做爲接口,爲視圖的展現提供數據源的做用。咱們應該將更多的與 Model 層有關的業務邏輯移到 Model 中以控制 Controller 的複雜性。

相關文章
相關標籤/搜索