5

所以我最近才开始使用 ReactiveCocoa,我认为最好的学习方法就是直接进入并开始重构我拥有的一些现有代码。我想得到一些批评,并确保我朝着正确的方向前进。

所以在我正在重构的应用程序中,我有大量的代码是这样的:

[self.ff getArrayFromUri:@"/States?sort=name asc" onComplete:^(NSError *theErr, id theObj, NSHTTPURLResponse *theResponse) {
    if(!theErr) {
       //do something with theObj
    }
    else {
       //handle the error
    }
}];

我目前在 ReactiveCocoa 中对此进行了重构,如下所示:

-(void)viewDidLoad {
 //ReactiveCocoa
RACCommand *command = [RACCommand command];
RACSubject *subject = [RACSubject subject];
[[[command addSignalBlock:^RACSignal *(id value) {
    NSError *err;
    NSArray *array = [self.ff getArrayFromUri:@"/States" error:&err];
    err ? [subject sendError:err] : [subject sendNext:array];
    return [RACSignal empty];
}]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]];

[subject subscribeNext:^(NSArray *x) {
    [self performSegueWithIdentifier:kSomeSegue sender:x];
} error:^(NSError *error) {
    NSLog(@"the error = %@", error.localizedDescription);
}];

self.doNotLocation = [UIButton buttonWithType:UIButtonTypeCustom];
[self.doNotLocation setBackgroundImage:[UIImage imageNamed:@"BlackButton_small.png"] forState:UIControlStateNormal];
[[self.doNotLocation rac_signalForControlEvents:UIControlEventTouchUpInside] executeCommand:command];
RAC(self.doNotLocation.enabled) = RACAbleWithStart(command, canExecute);
RAC([UIApplication sharedApplication],networkActivityIndicatorVisible) = RACAbleWithStart(command, executing);    }

这是关于我应该如何处理它,使用 RACSubject,还是有更好的方法?这整个概念对我来说是新的,因为到目前为止我唯一的编程语言是 Java 和 Objective-C,所以这种功能反应式的思维方式让我有点失望。

4

2 回答 2

11

不幸的是,您提供的代码示例存在几个问题:

  1. 传递给的块-addSignalBlock:返回一个空信号。这应该是一个警告标志,因为几乎没有垃圾返回值。在这种情况下,这意味着块同步执行其工作。为了避免阻塞主线程,你应该创建一个异步工作的信号,然后返回它。
  2. 并且没有做任何事情-switchToLatest-deliverOn:大多数信号运算符仅在订阅了结果信号时才起作用。在这种情况下,它只是消失在以太中。

我们可以同时解决这两个问题。-addSignalBlock:返回块中返回的信号的信号。如果我们返回一些有意义的东西,它可以在该方法之外处理。

首先,这需要添加到顶部:

@weakify(self);

@strongify(self)在下面使用时,它将防止保留循环。这是必要的,因为RACCommand生命只要self

现在,内部信号的创建:

RACSignal *requestSignals = [command addSignalBlock:^(id value) {
    return [RACSignal start:^(BOOL *success, NSError **err) {
        @strongify(self);

        NSArray *array = [self.ff getArrayFromUri:@"/States" error:err];
        *success = (array != nil);

        return array;
    }];
}];

在块内,这只是创建一个信号,该信号将调用-getArrayFromUri:error:并传回其结果或错误(如果发生)。+start:将确保工作在后台进行。

从所有这些中,我们得到requestSignals,这是那些创建的信号的信号。这可以完全替代RACSubject原来使用的:

RACSignal *arrays = [[[requestSignals
    map:^(RACSignal *request) {
        return [request catch:^(NSError *error) {
            NSLog(@"the error = %@", error);
            return [RACSignal empty];
        }];
    }]
    flatten]
    deliverOn:RACScheduler.mainThreadScheduler];

首先,我们将每个内部信号转换为记录,然后忽略错误。(这有点复杂,但将来可能会添加一个 RAC 运算符来完成它。)

然后我们将信号的信号展平。结果 ,arrays是一个通过所有内部信号值的信号。这就是我们必须忽略错误的原因——如果其中任何一个达到这一点,我们将永远停止从内部信号中获取所有值。

最后,我们“提升”选择器来调用:

[self rac_liftSelector:@selector(performSegueWithIdentifier:sender:) withObjects:kSomeSegue, arrays];

-performSegueWithIdentifier:sender:每当arrays发送一个新值(将从网络返回)时,这将重新发送NSArray。你可以把它想象成随着时间的推移调用一个方法。这比订阅要好,因为它简化了副作用和内存管理。

于 2013-05-28T09:21:09.653 回答
1

根据我对框架的经验,我发现直接使用的理由很少RACSubject,尤其是对于像这样的一次性信号。RACSubjects 表示可变信号,在这种情况下您不需要,实际上会增加代码的复杂性。在该命令块内返回一个普通信号(通过)会好得多+[RACSignal createSignal:],然后让生成的请求代码构成信号的主体:

[[[command addSignalBlock:^RACSignal *(id value) {
    //
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        //Request code here
            return nil;
    }];
}]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]]; 

或者,更好的是,您可以重构getArrayFromUri:error:以返回信号并摆脱该三元语句:

 [[[command addSignalBlock:^RACSignal *(id value) {
     return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        //...
        [[self getArrayFromUri:@"/States"]subscribeError:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendNext:array];
        }];
            return nil;
        }];
  }]switchToLatest]deliverOn:RACScheduler.mainThreadScheduler];

至于下一行的订阅问题,可以认为是信号的副作用,所以我们可以明确地将它们do:应用到命令的信号的相应变体中:

    [[[command addSignalBlock:^RACSignal *(id value) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [[[[self getArrayFromUri:@"/States"]doError:^(NSError *error) {
                NSLog(@"the error = %@", error.localizedDescription);
                [subscriber sendError:err];
            }] doNext:^(NSArray *array) {
                [subscriber sendNext:array];
                [self performSegueWithIdentifier:kSomeSegue sender:array];
            }] subscribeCompleted:^{
                [subscriber sendCompleted];
            }];
            return [RACDisposable disposableWithBlock:^{
                 // Cleanup
            }];
        }];
    }]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]]; 

最后,由于命令与信号的工作方式不同,因此不会评估最外层的运算符(感谢 @jspahrsummers),因此您可以删除它们。

[command addSignalBlock:^RACSignal *(id value) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [[[[self getArrayFromUri:@"/States"]doError:^(NSError *error) {
                    NSLog(@"the error = %@", error.localizedDescription);
                    [subscriber sendError:err];
                }] doNext:^(NSArray *array) {
                    [subscriber sendNext:array];
                    [self performSegueWithIdentifier:kSomeSegue sender:array];
                }] subscribeCompleted:^{
                    [subscriber sendCompleted];
                }];
                return [RACDisposable disposableWithBlock:^{
                     // Cleanup
                }];
            }];
        }]; 
于 2013-05-27T03:37:42.227 回答