重构为ReactiveCocoa

ter*_*wis 5 refactoring objective-c ios reactive-cocoa

所以我刚刚开始使用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
    }
}];
Run Code Online (Sandbox Code Playgroud)

我目前在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);    }
Run Code Online (Sandbox Code Playgroud)

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

Jus*_*ers 11

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

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

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

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

@weakify(self);
Run Code Online (Sandbox Code Playgroud)

@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;
    }];
}];
Run Code Online (Sandbox Code Playgroud)

在块内,这只是创建一个信号-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];
Run Code Online (Sandbox Code Playgroud)

首先,我们将每个内部信号转换为log,然后忽略错误.(这有点复杂,但未来可能会添加一个RAC操作员.)

然后我们平坦化信号的信号.结果,arrays是通过所有内部信号的值的信号.这就是为什么我们必须忽略错误 - 如果它们中的任何一个达到这一点,我们将永远停止从内部信号中获取所有值.

最后,我们"提升"选择器以调用:

[self rac_liftSelector:@selector(performSegueWithIdentifier:sender:) withObjects:kSomeSegue, arrays];
Run Code Online (Sandbox Code Playgroud)

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