获取节点的值

优质
小牛编辑
124浏览
2023-12-01

一个节点就象征着一个数据,现在的或者未来的,它是一种预期,所以我们也对应的有获取即时值和获取未来值两种方式。

获取即时值

对于一个节点来说,访问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