这是我参与「第四届青训营 」笔记创作活动的第5天
对比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层
- 大多数情况下服务的发起都是在 Controller 中进行的;
- 然后会在 HTTP 请求的回调中交给模型层处理 JSON 数据;
- 返回开箱即用的对象交还给 Controller 控制器;
- 最后由 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提供数据源