获取节点的值
一个节点就象征着一个数据,现在的或者未来的,它是一种预期,所以我们也对应的有获取即时值和获取未来值两种方式。
获取即时值
对于一个节点来说,访问T value
属性是最简单有效的形式,但是由于空值,我们可能需要特殊注意一下:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
NSNumber *value = node.value; // <- EZREmpty.empty !!!
node.value = @33;
value = node.value; // <- @33
[node clean];
value = node.value; // <- EZREmpty.empty !!!
所以我们在使用的时候不得不进行类型判断,可以对节点进行判断,也可以对返回值进行判断,像这样:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
if([node isEmpty]) { // <- 也可以是 node.empty
NSNumber *value = node.value;
// 做你想做的事情吧
}
// 或者这样:
NSNumber *value = node.value;
if ([value isKindOfClass:NSNumber.class]) {
// 做你想做的事情吧
} else {
value = @0;
}
就像后面的例子那样,你很可能想在空值的时候给个默认值,这时- (nullable T)valueWithDefault:(nullable T)defaultValue
方法可能对你很有帮助:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
NSNumber *value = [node valueWithDefault:@0]; // <- @0
node.value = @33;
value = [node valueWithDefault:@0]; // <- @33
而对于前面的例子,你只是想要在非空值的时候才做一些动作,则可以使用- (void)getValue:(void(NS_NOESCAPE ^ _Nullable)(_Nullable T value))processBlock
方法:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
[node getValue:^(NSNumber *value) {
// 不会执行
}];
node.value = @33;
[node getValue:^(NSNumber *value) {
// 做你想做的事情吧
}];
监听节点值
区别于前面的立即值获取,我们可能对一个节点的未来值感兴趣,这就需要通过监听的手段。根据 FrameworkOverview 中描述的,我们在监听的过程中,需要监听者这样一个对象,它负责维持这个监听行为。
最简单的监听方式就像这样:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
NSObject *listener = [NSObject new];
[[node listenedBy:listener] withBlock:^(NSNumber *next) {
NSLog(@"下一个值是 %@", next);
}];
node.value = @2;
[node clean];
node.value = @3;
dispatch_async(dispatch_get_main_queue(), ^{
node.value = @4;
});
它的结果如下:
下一个值是 1
下一个值是 2
下一个值是 3
通过观察不难发现,在监听过程中我们不会收到空值,并且当监听者不存在的时候,监听的行为也不会执行。
前面也提到过上下文对象的传递,我们可以通过withContextBlock:
方法来获得其上下文:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
NSObject *listener = [NSObject new];
[[node listenedBy:listener] withContextBlock:^(NSNumber *next, id context) {
NSLog(@"下一个值是 %@,上下文是 %@", next, context);
}];
[node setValue:@2 context:@"嘿,是我"];
它的结果如下:
下一个值是 1,上下文是 (null)
下一个值是 2,上下文是 嘿,是我
有的时候,我们可能直接调用监听者的方法,像这样:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
self.someView = [UIView new];
@ezr_weakify(self)
[[node listenedBy:self.someView] withBlock:^(NSNumber *_) {
@ezr_strongify(self)
[self.someView removeFromSuperview];
}];
这样的代码不但重复,而且还需要 weakify-strongify。所以 EasyReact 专门为这种情况提供了 withSelector:
方法:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
self.someView = [UIView new];
[[node listenedBy:self.view] withSelector:@selector(removeFromSuperview)];
这样写起来就简单多了,withSelector:
的参数 selector
签名在不同参数个数时行为有所不同:
- 没有参数时会直接调用该 selector 的函数。
- 一个参数时会调用该 selector 的函数并将监听到的新值以第一参数的形式传入。
- 两个参数时会调用该 selector 的函数并将监听到的新值以第一参数的形式传入,并将上下文对象以第二参数的形式传入。
多线程下的监听
默认情况下,设置线程和监听线程是一致的,例如:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
[NSThread currentThread].threadDictionary[@"flag"] = @"这是主线程";
[[node listenedBy:self] withBlock:^(NSNumber *next) {
NSLog(@"%@:现在收到了 %@", [NSThread currentThread].threadDictionary[@"flag"], next);
}];
NSLog(@"node 已经进行监听了");
node.value = @2;
NSLog(@"node 值已经设置为 2 了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].threadDictionary[@"flag"] = @"这是某个子线程";
node.value = @3;
NSLog(@"node 值已经设置为 3 了");
});
它的结果如下:
这是主线程:现在收到了 1
node 已经进行监听了
这是主线程:现在收到了 2
node 已经值设置为 2 了
这是某个子线程:现在收到了 3
node 已经值设置为 3 了
也许这正是你所需要的,但是一不小心就可能造成错误,例如在子线程更新 UI:
EZRMutableNode<NSString *> *node = [EZRMutableNode value:@"你好,世界"];
@ezr_weakify(self)
[[node listenedBy:self] withBlock:^(NSString *next) {
@ezr_strongify(self)
self.someLabel.text = next;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
node.value = @"一个崩溃在等着你";
});
相对的,如果监听行为非常耗时,在主线程监听到新的值会直接让程序无响应:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
[[node listenedBy:self] withBlock:^(NSNumber *next) {
for (int i = 0; i < next.intValue; ++i) {
NSLog(@"报数:%d", i);
}
}];
node.value = @19999999;
// 天啊,还没执行到我
使用withBlock:on:
或者withBlockOnMainQueue:
就可以帮助我们解决此类问题:
EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
[[node listenedBy:self] withBlockOnMainQueue:^(NSNumber *next) {
NSString *thread = [[NSThread currentThread] isMainThread] ? @"主线程" : @"子线程";
NSLog(@"[监听1]%@:现在收到了 %@", thread, next);
}];
[[node listenedBy:self] withBlock:^(NSNumber *next) {
NSString *thread = [[NSThread currentThread] isMainThread] ? @"主线程" : @"子线程";
NSLog(@"[监听2]%@:现在收到了 %@", thread, next);
} on:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
NSLog(@"node 已经进行监听了");
node.value = @2;
NSLog(@"node 值已经设置为 2 了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].threadDictionary[@"flag"] = @"这是某个子线程";
node.value = @3;
NSLog(@"node 值已经设置为 3 了");
});
它的结果如下:
node 已经进行监听了
[监听2]子线程:现在收到了 1
node 值已经设置为 2 了
[监听2]子线程:现在收到了 2
node 值已经设置为 3 了
[监听2]子线程:现在收到了 3
[监听1]主线程:现在收到了 1
[监听1]主线程:现在收到了 2
[监听1]主线程:现在收到了 3