KVO
和 KVC
是 Objective-C
语言非常强大的两个特性,从一开始的似懂非懂到慢慢了解它的底层实现,才感受到这门动态语言的魅力所在。
KVC
允许通过一个点分隔的字符串来设置一个对象的属性值,而 KVO
可以通过点分隔的字符串来监听对象属性值的改变。
@interface Foo : NSObject
@property (nonatomic) NSString *name;
@property (nonatomic) NSInteger age;
@end
@interface Bar : NSObject
@property (nonatomic) Foo *foo;
@end
Bar *bar = [Bar new];
[bar addObserver:self
forKeyPath:@"foo.name"
options:NSKeyValueObservingOptionNew
context:NULL];
[bar setValue:@20 forKeyPath:@"foo.age"]; // 当然这个是无效的,因为 foo 为 nil
既然是字符串,人就可能犯错,因为它无法自动补全。比如常见的拼写错误,或者一个不存在的 keypath。如何能让 Xcode 在编译时就检查出这些错误呢?
受 libextobjc 的启发,本人作了一个宏来处理此事,编译时检查错误,且能自动补全。
#define __mz_macro_concat(A, B) __mz_macro_concat_(A, B)
#define __mz_macro_argcount(...) __mz_macro_at(6, __VA_ARGS__, 6, 5, 4, 3, 2, 1)
#define __mz_macro_head(...) __mz_macro_head_(__VA_ARGS__, 0)
#define __mz_macro_at(N, ...) __mz_macro_concat(__mz_macro_at, N)(__VA_ARGS__)
#define __mz_macro_at0(...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at1(_0, ...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at2(_0, _1, ...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at3(_0, _1, _2, ...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at4(_0, _1, _2, _3, ...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at5(_0, _1, _2, _3, _4, ...) __mz_macro_head(__VA_ARGS__)
#define __mz_macro_at6(_0, _1, _2, _3, _4, _5, ...) __mz_macro_head(__VA_ARGS__)
#define MZKVOKeyPath(Class, ...) __mz_macro_concat(MZKVOKeyPath, __mz_macro_argcount(__VA_ARGS__))(Class, __VA_ARGS__)
#define MZKVOKeyPath0(Class) ((Class *)nil)
#define MZKVOKeyPath1(Class, path0) ((void)(NO && ((void)(((Class *)nil).path0), NO)), @#path0)
#define MZKVOKeyPath2(Class, path0, path1) ((void)(NO && ((void)((((Class *)nil).path0).path1), NO)), @#path0 "." #path1)
#define MZKVOKeyPath3(Class, path0, path1, path2) ((void)(NO && ((void)(((((Class *)nil).path0).path1).path2), NO)), @#path0 "." #path1 "." #path2)
#define MZKVOKeyPath4(Class, path0, path1, path2, path3) ((void)(NO && ((void)((((((Class *)nil).path0).path1).path2).path3), NO)), @#path0 "." #path1 "." #path2 "." #path3)
#define MZKVOKeyPath5(Class, path0, path1, path2, path3, path4) ((void)(NO && ((void)(((((((Class *)nil).path0).path1).path2).path3).path4), NO)), @#path0 "." #path1 "." #path2 "." #path3 "." #path4)
#define MZKVOKeyPath6(Class, path0, path1, path2, path3, path4, path5) ((void)(NO && ((void)((((((((Class *)nil).path0).path1).path2).path3).path4).path5), NO)), @#path0 "." #path1 "." #path2 "." #path3 "." #path4 "." #path5)
#define __mz_macro_concat_(A, B) A ## B
#define __mz_macro_head_(FIRST, ...) FIRST
使用方法
在 MZKVOKeyPath
宏中,第一个参数传一个 Class 类型,后面跟它的实例的属性,就像使用 .
点操作一样,可以一级一级往下。
[bar addObserver:self
forKeyPath:MZKVOKeyPath(Bar, foo, name) // 打了 Bar 之后,Xcode 会自动补全
options:NSKeyValueObservingOptionNew
context:NULL];
注意:最多支持到六层 keypath,需要更多您可以自己修改。
网友评论