KVOExt
Simplify work with KVO.
Features
- simple syntax
- strongly typed keypath
- automatic observer removal
- handler blocks implicitly retained by listener
- handler block does not retain self
- support lazy binding
- can replace delegation, target-action patterns
- NOT THREAD SAFE (work only on main thread)
Usage
Bind/observe
observe(src, propName) { ... }
bind(src, propName) { ... }observe - start listening src.propName, handler block will be triggered on next calling of propName setter.
bind - start listening src.propName and immediately trigger handler block.
In handler block use keyword value to access to observable value.
Manual unbind
bind(src, propName, key) { ... }
unbind(key)key - like key for dictionary, should confirm to the NSCopying, typical it's string.
Lazy binding
Use class name instead source object for lazy binding.
bind(ClsName, propName) { ... }
self.dataContext = src;Binding "activate" after dataContext assigned.
dataContext can be set before binding declaration.
src class should be kind of ClsName.
Each NSObject has implict property dataContext.
Event emulation macros
Declare event. Second optional param - argument type.
event_prop(change, MyClass*); // @property (nonatomic) MyClass* change;
event_prop(loading, BOOL); // @property (nonatomic) BOOL loading;
event_prop(complete); // @property (nonatomic) id complete;Raise event
event_raise(loading, YES); // self.loading = YES;
event_raise(complete); // self.complete = nil;Listen event
observe(src, loading) { ... }Start/stop observing
Execute some code on start/stop observing of properties. Useful for UI. See examples.
on_start_observing(ClsName, propName) {
// <start observing code >
};
on_stop_observing(ClsName, propName) {
// <stop observing code >
};Examples
Reactive style, dependence on multiple sources
@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];
}
@endObserve self properties (write less damn code)
@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;
};
}
@endUIButton click helper
@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");
};
}
@endMVVM
@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";
};
}
@endLicense
This project is licensed under the MIT License - see the LICENSE file for details