KVOExt
简化 KVO 的工作。
特性
- 简单语法
- 强类型 keypath
- 自动观察者移除
- 处理块隐式由监听器保留
- 处理块不保留 self
- 支持延迟绑定
- 可以替换代理、目标-动作模式
- 非线程安全(仅在主线程上工作)
使用
绑定/观察
observe(src, propName) { ... }
bind(src, propName) { ... }
观察
- 开始监听 src.propName,当名为 propName 的属性设置器被调用时将触发处理块。
绑定
- 开始监听 src.propName 并立即触发处理块。在处理块中使用关键字 值
来访问可观察的值。
手动解绑
bind(src, propName, key) { ... }
unbind(key)
键
- 类似于字典的键,应符合 NSCopying 规范,通常是字符串。
懒加载绑定
使用类名代替源对象进行懒加载绑定。
bind(ClsName, propName) { ... }
self.dataContext = src;
在 dataContext 分配后绑定 "激活"。
dataContext
在绑定声明之前可以设置。
src
类应该是 ClsName
类型。
每个 NSObject 都隐含了 dataContext
属性。
事件模拟宏
声明事件。第二个可选参数为参数类型。
event_prop(change, MyClass*); // @property (nonatomic) MyClass* change;
event_prop(loading, BOOL); // @property (nonatomic) BOOL loading;
event_prop(complete); // @property (nonatomic) id complete;
触发事件
event_raise(loading, YES); // self.loading = YES;
event_raise(complete); // self.complete = nil;
监听事件
observe(src, loading) { ... }
开始/停止观察
在属性的开/始和停止观察时执行一些代码。对 UI 很有用。请参阅示例。
on_start_observing(ClsName, propName) {
// <start observing code >
};
on_stop_observing(ClsName, propName) {
// <stop observing code >
};
示例
响应式样式,依赖于多个数据源
@interface MyModel : NSObject
@property (nonatomic) NSString* name;
@property (nonatomic) NSString* surname;
@property (nonatomic) NSInteger age;
@end
@interface MyViewModel : NSObject
@property (nonatomic) NSString* fullname;
@property (nonatomic) NSString* age;
@end
@implementation MyViewModel
{
MyModel* _model;
}
- (instancetype)initWithModel:(MyModel*)model {
self = [super init];
if (self) {
_model = model;
bind(model, age) { self.age = [NSString stringWithFormat:@"age: %@ ", @(value)]; };
observe(model, name) { [self check]; };
observe(model, surname) { [self check]; };
[self check];
}
return self;
}
-(void)check {
self.fullname = [NSString stringWithFormat:@"%@ %@", _model.name, _model.surname];
}
@end
观察自身属性(少写一些错误代码)
@interface MyView : UIView
@property (nonatomic) NSString* text;
@end
@implementation MyView
-(void)setup {
UILabel* label = [UILabel new];
[self addSubview:label];
observe(self, text) {
label.text = value;
};
}
@end
UIButton点击助手
@interface UIButton (ClickHelper)
event_prop(click);
@end
@implementation UIButton (ClickHelper)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
on_start_observing(UIButton, click) {
[self addTarget:self action:@selector(_onTouchUpInside) forControlEvents:UIControlEventTouchUpInside];
};
on_stop_observing(UIButton, click) {
if (!inDealloc) {
[self removeTarget:self action:@selector(_onTouchUpInside) forControlEvents:UIControlEventTouchUpInside];
}
};
});
}
-(void)_touchUpInside {
event_raise(click);
}
-(id)click { return nil; }
-(void)setClick:(id)click {}
@end
@implementation MyView
-(void)setup {
UIButton* but = [UIButton new];
[self addSubview:but];
observe(but, click) {
NSLog(@"button click");
};
}
@end
MVVM
@interface MyViewModel : NSObject
@property (nonatomic) NSString* text;
@end
@implementation MyView
-(void)setup {
UILabel* label = [UILabel new];
[self addSubview:label];
bind(MyViewModel, text) {
label.text = value;
};
}
@end
@implementation ViewController
-(void)viewDidLoad {
MyView* view = [MyView new];
[self.view addSubview:view];
MyViewModel* vm = [MyViewModel new]; // it's not good instatiate view model in view controller, just for example
vm.text = @"hello";
view.dataContext = vm;
observe(self.view, tap) {
vm.text = @"hello, world";
};
}
@end
许可证
本工程采用MIT许可证 - 详细内容请见LICENSE文件。