获取节点的值

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

获取即时值

对于一个节点来说,访问T value属性是最简单有效的形式,但是由于空值,我们可能需要特殊注意一下:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
  2. NSNumber *value = node.value; // <- EZREmpty.empty !!!
  3. node.value = @33;
  4. value = node.value; // <- @33
  5. [node clean];
  6. value = node.value; // <- EZREmpty.empty !!!

所以我们在使用的时候不得不进行类型判断,可以对节点进行判断,也可以对返回值进行判断,像这样:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
  2. if([node isEmpty]) { // <- 也可以是 node.empty
  3. NSNumber *value = node.value;
  4. // 做你想做的事情吧
  5. }
  6. // 或者这样:
  7. NSNumber *value = node.value;
  8. if ([value isKindOfClass:NSNumber.class]) {
  9. // 做你想做的事情吧
  10. } else {
  11. value = @0;
  12. }

就像后面的例子那样,你很可能想在空值的时候给个默认值,这时- (nullable T)valueWithDefault:(nullable T)defaultValue方法可能对你很有帮助:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
  2. NSNumber *value = [node valueWithDefault:@0]; // <- @0
  3. node.value = @33;
  4. value = [node valueWithDefault:@0]; // <- @33

而对于前面的例子,你只是想要在非空值的时候才做一些动作,则可以使用- (void)getValue:(void(NS_NOESCAPE ^ _Nullable)(_Nullable T value))processBlock方法:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode new];
  2. [node getValue:^(NSNumber *value) {
  3. // 不会执行
  4. }];
  5. node.value = @33;
  6. [node getValue:^(NSNumber *value) {
  7. // 做你想做的事情吧
  8. }];

监听节点值

区别于前面的立即值获取,我们可能对一个节点的未来值感兴趣,这就需要通过监听的手段。根据 FrameworkOverview 中描述的,我们在监听的过程中,需要监听者这样一个对象,它负责维持这个监听行为。

最简单的监听方式就像这样:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. NSObject *listener = [NSObject new];
  3. [[node listenedBy:listener] withBlock:^(NSNumber *next) {
  4. NSLog(@"下一个值是 %@", next);
  5. }];
  6. node.value = @2;
  7. [node clean];
  8. node.value = @3;
  9. dispatch_async(dispatch_get_main_queue(), ^{
  10. node.value = @4;
  11. });

它的结果如下:

  1. 下一个值是 1
  2. 下一个值是 2
  3. 下一个值是 3

通过观察不难发现,在监听过程中我们不会收到空值,并且当监听者不存在的时候,监听的行为也不会执行。

前面也提到过上下文对象的传递,我们可以通过withContextBlock:方法来获得其上下文:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. NSObject *listener = [NSObject new];
  3. [[node listenedBy:listener] withContextBlock:^(NSNumber *next, id context) {
  4. NSLog(@"下一个值是 %@,上下文是 %@", next, context);
  5. }];
  6. [node setValue:@2 context:@"嘿,是我"];

它的结果如下:

  1. 下一个值是 1,上下文是 null)
  2. 下一个值是 2,上下文是 嘿,是我

有的时候,我们可能直接调用监听者的方法,像这样:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. self.someView = [UIView new];
  3. @ezr_weakify(self)
  4. [[node listenedBy:self.someView] withBlock:^(NSNumber *_) {
  5. @ezr_strongify(self)
  6. [self.someView removeFromSuperview];
  7. }];

这样的代码不但重复,而且还需要 weakify-strongify。所以 EasyReact 专门为这种情况提供了 withSelector: 方法:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. self.someView = [UIView new];
  3. [[node listenedBy:self.view] withSelector:@selector(removeFromSuperview)];

这样写起来就简单多了,withSelector: 的参数 selector 签名在不同参数个数时行为有所不同:

  • 没有参数时会直接调用该 selector 的函数。
  • 一个参数时会调用该 selector 的函数并将监听到的新值以第一参数的形式传入。
  • 两个参数时会调用该 selector 的函数并将监听到的新值以第一参数的形式传入,并将上下文对象以第二参数的形式传入。

多线程下的监听

默认情况下,设置线程和监听线程是一致的,例如:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. [NSThread currentThread].threadDictionary[@"flag"] = @"这是主线程";
  3. [[node listenedBy:self] withBlock:^(NSNumber *next) {
  4. NSLog(@"%@:现在收到了 %@", [NSThread currentThread].threadDictionary[@"flag"], next);
  5. }];
  6. NSLog(@"node 已经进行监听了");
  7. node.value = @2;
  8. NSLog(@"node 值已经设置为 2 了");
  9. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  10. [NSThread currentThread].threadDictionary[@"flag"] = @"这是某个子线程";
  11. node.value = @3;
  12. NSLog(@"node 值已经设置为 3 了");
  13. });

它的结果如下:

  1. 这是主线程:现在收到了 1
  2. node 已经进行监听了
  3. 这是主线程:现在收到了 2
  4. node 已经值设置为 2
  5. 这是某个子线程:现在收到了 3
  6. node 已经值设置为 3

也许这正是你所需要的,但是一不小心就可能造成错误,例如在子线程更新 UI:

  1. EZRMutableNode<NSString *> *node = [EZRMutableNode value:@"你好,世界"];
  2. @ezr_weakify(self)
  3. [[node listenedBy:self] withBlock:^(NSString *next) {
  4. @ezr_strongify(self)
  5. self.someLabel.text = next;
  6. }];
  7. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  8. node.value = @"一个崩溃在等着你";
  9. });

相对的,如果监听行为非常耗时,在主线程监听到新的值会直接让程序无响应:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. [[node listenedBy:self] withBlock:^(NSNumber *next) {
  3. for (int i = 0; i < next.intValue; ++i) {
  4. NSLog(@"报数:%d", i);
  5. }
  6. }];
  7. node.value = @19999999;
  8. // 天啊,还没执行到我

使用withBlock:on:或者withBlockOnMainQueue:就可以帮助我们解决此类问题:

  1. EZRMutableNode<NSNumber *> *node = [EZRMutableNode value:@1];
  2. [[node listenedBy:self] withBlockOnMainQueue:^(NSNumber *next) {
  3. NSString *thread = [[NSThread currentThread] isMainThread] ? @"主线程" : @"子线程";
  4. NSLog(@"[监听1]%@:现在收到了 %@", thread, next);
  5. }];
  6. [[node listenedBy:self] withBlock:^(NSNumber *next) {
  7. NSString *thread = [[NSThread currentThread] isMainThread] ? @"主线程" : @"子线程";
  8. NSLog(@"[监听2]%@:现在收到了 %@", thread, next);
  9. } on:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
  10. NSLog(@"node 已经进行监听了");
  11. node.value = @2;
  12. NSLog(@"node 值已经设置为 2 了");
  13. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  14. [NSThread currentThread].threadDictionary[@"flag"] = @"这是某个子线程";
  15. node.value = @3;
  16. NSLog(@"node 值已经设置为 3 了");
  17. });

它的结果如下:

  1. node 已经进行监听了
  2. [监听2]子线程:现在收到了 1
  3. node 值已经设置为 2
  4. [监听2]子线程:现在收到了 2
  5. node 值已经设置为 3
  6. [监听2]子线程:现在收到了 3
  7. [监听1]主线程:现在收到了 1
  8. [监听1]主线程:现在收到了 2
  9. [监听1]主线程:现在收到了 3