谈谈 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 的复杂性。

相关文章
相关标签/搜索