有更简单的方法,以下不要再看了吧。。。
#define MZKeyPath(OBJ, PATH) \
(((void)(NO && ((void)(((typeof(OBJ))nil).PATH), NO)), @# PATH))
// 支持类或实例两种写法:
- (void)viewDidLoad
{
[super viewDidLoad];
MZKeyPath(NSString *, lowercaseString.uppercaseString.length);
MZKeyPath(self, title.lowercaseString.uppercaseString.UTF8String);
}
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 的启发,本人作了一个宏来处理此事,编译时检查错误,且能自动补全。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 类型,后面跟它的实例的属性,就像使用 .
点操作一样,可以一级一级往下。
注意:最多支持到六层 keypath,需要更多您可以自己修改。