正文
除了 YYModel,我们也可以使用 Mantle 等框架在 OC 中解决 JSON 到模型的转换的问题。
从上面的代码,我们可以看出:Objective-C 和 Swift 对于相同功能的处理,却有较大差别的实现。这种情况的出现主要原因是语言的设计思路导致的;Swift 一直鼓吹自己有着较强的安全性,能够写出更加稳定可靠的应用程序,而安全性来自于 Swift 语言的设计哲学;由此看来静态类型、安全和动态类型、元编程能力(?)看起来是比较难以共存的。
其实很多静态编程语言,比如 C、C++ 和 Rust 都通过宏实现了比较强大的元编程能力,虽然 Swift 也通过模板在元编程支持上做了一些微小的努力,不过到目前来看( 3.0 )还是远远不够的。
OC 中对于 nil 的处理能够减少我们在编码时的工作量,不过也对工程师的代码质量提出了考验。我们需要思考 nil 的出现会不会带来崩溃,是否会导致行为的异常、增加应用崩溃的风险以及不确定性,而这也是 Swift 引入 Optional 这一概念来避免上述问题的初衷。
相比而言,笔者还是更喜欢强大的元编程能力,这样可以减少大量的重复工作并且提供更多的可能性,与提升工作效率相比,牺牲一些安全性还是可以接受的。
现有的大多数应用都会将网路服务组织成单独的一层,所以有时候你会看到所谓的 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 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,这种网络服务层中的请求都是以配置的形式实现的,需要对原有的命令式的请求进行一层封装,也就是说所有的参数 requestURL、method 和 parameters 都应该以配置的形式声明在每一个 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)"
}
}
我们在这里写了两个简单的 Request 类 AllUsersRequest 和 FindUserRequest,它们两个一个负责获取所有的 User 对象,一个负责从服务端获取指定的 User;在使用上面的声明式 Service 层时也与命令式有一些不同: