《谈谈 MVX 中的 Model 》笔记 | 青训营笔记

120 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

谈谈 MVX 中的 Model - 面向信仰编程

对比swift和oc的json → model

swift由于存在Option类型,需要考虑json中这个字段的值是否存在

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)
    }
}

而OC不关心空值。为什么OC没有这个问题?在OC中并不在乎对象是否为空,向nil对象发送消息不会造成崩溃,好处是保证了程序的灵活性,坏处是当nil导致了程序的崩溃比较难以定位nil出现的原始位置。

// 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// 也可以使用YYmodel
User *user = [User yy_modelWithJSON:json];

元编程

作者提到了元编程的能力

怎么理解元编程?

you write code that writes code.

('A'..'Z').each do |char|
    system("python -c 'print "#{char}"'")
end

网络服务Service层

Untitled.png

  1. 大多数情况下服务的发起都是在 Controller 中进行的;
  2. 然后会在 HTTP 请求的回调中交给模型层处理 JSON 数据;
  3. 返回开箱即用的对象交还给 Controller 控制器;
  4. 最后由 View 层展示服务端返回的数据;

有两种组织Service的方式

  • 命令式

Service层为每一个或一组API写一个专门用于Http请求的Manager类,在Service中直接填充Model,在回调函数中返回。调用 Service 服务的 Controller 可以直接从回调中使用构建好的 Model 对象。

  • 网络请求
  • 数据转换 JSON
  • JSON 转换到模型
  • completion 回调
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)
            }
        }
    }
}
​

在2022年08月字节青训营开发的天气App中,我的做法。我没有直接把数据转成model传到回调函数里,而是将数据放到service的数组中,不太清楚这样做是否是最优的。

@implementation NowDataService
​
- (instancetype)init{
    if(self = [super init]){
​
    }
    return self;
}
​
- (void)fetchData:(CityModel*)city callback:(void (^)(void))callBack{
​
    NSDictionary * params = @{
        @"location" : [NSString getCityCode:city.cityCode],
        @"key" : WEATHER_KEY
    };
    
    [[NetworkManager shared] simpleGet:NOW_API parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
​
        __typeof__(self) __weak wself = self;
​
        NSDictionary* data = responseObject[@"now"];
        wself.nowModel = [[NowModel alloc] initWithDict:data];
        callBack();
​
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"======%@",error);
        }];
}
  • 声明式

定义一个Abstract Request ,再去分别定义具体的请求比如创建用户的请求、获取用户数据的请求,这些请求都要继承这个抽象类。

这个抽象类是干嘛的呢,用来定义一个请求需要的全部参数。如果是在 Objective-C 中,一般会定义一个抽象的基类,并让所有的 Request 都继承它;但是在 Swift 中,我们可以使用协议以及协议扩展的方式实现这一功能。

在 AbstractRequest 协议中定义了发出一个请求所需要的全部参数以及发起请求的动作start

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)
            }
        }
    }
}

调用的时候json无法在Service层之间转换成model,因为server只提供了请求网络最基本的封装,所以生成model需要在回调函数里进行。

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)"
    }
}
​
// 使用
FindUserRequest(id: 1).start { json in
    if let json = json as? [String: Any],
        let user = User(json: json) {
        print(user)
    }
}
  • 命令式VS声明式

命令式的Manager中可以声明多个Api服务,而声明式的类与请求是一一对应的。

小结

MVC下的Model层只是对Json数据做了封装,配合Service层完成服务的请求

理想中的Model

作者在这里首先提到了要明确职责,客户端重展示,model层不是客户端最需要考虑的部分。

Model+Service有两个缺陷:

  • 如果按照API组织Service,Service的数量会随着API的数量增加而增加
  • 如果按照资源组织Service,为什么不把Service层中的代码直接放到Model层呢

作者就考虑直接在Model层restful,提供Http请求的接口

protocol RESTful {
    init?(json: [String: Any])
    static var url: String { get }
}
​
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 () -> ())
}
​
// 遵守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.index { users in
    // users
}
​
User.create(params: ["name": "Stark", "email": "example@email.com", "gender": 0, "age": 100]) { user in
    // user
}

缓存与持久化

OC和Swift缺少元编程的能力,比如下面这行代码可以生成一段SQL语句

User.find_by_name "draven"

但在Swift和OC中比较困难

总结

作者推荐让Model层提供Http请求、字段验证、持久化、为View提供数据源