iOS 去 Model 化的一种实践方式,通过 Protocol 的方式直接使用 Model。
不知道你有没有发现,现在项目越做越大,Model 越来越多,越来越重...
其实很多时候,我们使用 Model 的目的只是为了方便储存和读取一些数据,它的地位几乎等同于 Dictionary,只是 Dictionary 没有友好的访问方式罢了。
如果认同上面我说的话,或许你会需要 Protocol Model。
Model = Protocol + Dictionary
这就是 Protocol Model。
利用 Protocol 声明和 Dictionary 做 backend 支撑,不要创建专门的 Model 类便可以优雅的使用 Model。
同时,你可以理解为这是为 Dictionary 增加一个优雅的访问方式。
@protocol Human <YFProtocolModel>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
id<Human> human = YFProtocolModelCreate(@protocol(Human));
human.name = @"laizw";
human.age = 14;目前
Protocol Model只是个雏,需要轻点儿折腾~
- 属性支持 NS 类型
- 值类型(int, float, BOOL 等)
- 常用结构体(CGRect,CGPoint 等)
- 自定义结构体
- 自定义
setter和getter - JSON 转 Protocol Model
- 协议继承
- key 和属性名映射
- Model 嵌套
- 容器类属性
- KVO 支持
-
@optional 属性 (当前objc不支持读取@optional属性列表) - 类型转换
- Property Attribute 特性 (copy、weak等)
- ······
- (void)testDictJson2ProtocolModel {
NSDictionary *json = @{@"name": @"李四", @"age": @24};
id<Human> human = YFProtocolModelCreate(@protocol(Human), json);
XCTAssertTrue([human.name isEqualToString:@"李四"]);
XCTAssertEqual(human.age, 24);
}
- (void)testJsonString2ProtocolModel {
NSString *json = @"{\"name\":\"petter\",\"age\":30}";
id<Human> human = YFProtocolModelCreate(@protocol(Human), json);
XCTAssertTrue([human.name isEqualToString:@"petter"]);
XCTAssertEqual(human.age, 30);
}@protocol Student <Human>
@property (nonatomic, assign) NSInteger number;
@end
- (void)testInteritance {
NSDictionary *json = @{@"name": @"李四", @"age": @22, @"number": @123456};
id<Student> stu = YFProtocolModelCreate(@protocol(Student), json);
XCTAssertTrue([stu.name isEqualToString:@"李四"]);
XCTAssertEqual(stu.age, 22);
XCTAssertEqual(stu.number, 123456);
}众所周知,NSDictionary 是只能存入 Object 类型,不支持 struct、值类型等,所以在赋值和读取的时候 Protocol Model 会做一层装箱和拆箱处理。
因为内置了自动装箱拆箱机制,在使用 Number 值类型的时候可以当做正常 object 属性来使用。
内置支持一些常用的结构体
YFProtocolRegisterStruct(CGRect)
YFProtocolRegisterStruct(CGSize)
YFProtocolRegisterStruct(CGPoint)
YFProtocolRegisterStruct(NSRange)
YFProtocolRegisterStruct(UIOffset)
YFProtocolRegisterStruct(CGVector)
YFProtocolRegisterStruct(UIEdgeInsets)
YFProtocolRegisterStruct(CGAffineTransform)如果你需要用到其他 struct 或者需要自定义 struct,需要先注册该类型。
需要注意,定义结构体的时候不能定义匿名结构体
// ❌ 错误
typedef struct {
int arg;
} MyStruct;
// ✅ 正确
typedef struct MyStruct {
int arg;
} MyStruct;
// 注册只需要添加这么一句话
YFProtocolRegisterStruct(MyStruct)可以使用内置提供的方法,自动注册
YFProtocolDefineStruct(MyStruct, {
BOOL flag;
NSInteger num;
})通常,我们从接口得到的数据字段命名真是千奇百怪,这时候我们迫切需要属性名和 key 之间的映射。
一般的 JSON to Model 都实现了这个功能,但是他们都是基于 Class 实现的,基于 Class 可以很方便实现对应转换方法来进行数据处理,但是我们是 Protocol Model,那得需要点技巧了。。
@protocol Feed <YFProtocolModel>
@property NSString *uuid;
@end
@protocol implementation(Feed)
+ (NSDictionary<NSString *,id> *)modelPropertyKeyMapper {
return @{
@"uuid": @"id",
};
}
@end这样我们就可以随心所欲的操作数据了。
支持 Protocol 容器嵌套
@protocol Comment <YFProtocolModel>
@property NSString *uuid;
@end
@protocol Post <YFProtocolModel>
@property NSString *uuid;
@property NSArray<Comment> *comments;
@end
@protocol implementation(Post)
+ (NSDictionary<NSString *, Protocol *> *)modelContainerPropertyGenericProtocol {
return @{
@"comments": @protocol(Comment)
};
}
@end
- (void)testContainerGenerics {
NSDictionary *json = @{@"uuid": @"12345678",
@"comments": @[
@{@"uuid": @"1"},
@{@"uuid": @"2"},
@{@"uuid": @"3"},
@{@"uuid": @"4"},
]
};
id<Post> test = YFProtocolModelCreate(@protocol(Post), json);
NSLog(@"%@", [test.comments[3] uuid]);
}因为 KVO 是一个非正式协议,因此 Protocol Model 使用 KVO 需要手动加入对应 KVO 方法,目前方法已经在基础协议 YFProtocolModel 中定义,自定义 Model 只需要继承该协议便可。
@protocol KVOModelTests <YFProtocolModel>
@property NSString *name;
@end
- (void)testKVOProtocolModel {
id<KVOModelTests> test = YFProtocolModelCreate(@protocol(KVOModelTests));
[test addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
test.name = @"1";
test.name = @"2";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@", change[NSKeyValueChangeNewKey]);
}
------
1
2