重构为ReactiveCocoa

时间:2013-05-27 02:41:17

标签: ios objective-c refactoring 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
    }
}];

我目前在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);    }

这是关于我应该如何使用RACS主题,还是有更好的方法?这整个概念对我来说都是新的,因为到目前为止我唯一的编程语言是Java和Objective-C,所以这种功能性的反应式思维方式让我有些偏离。

2 个答案:

答案 0 :(得分:11)

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

  1. 传递给-addSignalBlock:的块返回一个空信号。这应该是一个警告标志,因为几乎没有垃圾返回值。在这种情况下,它意味着块同步执行其工作。为了避免阻塞主线程,你应该创建一个信号,它可以异步工作并返回它。
  2. -switchToLatest-deliverOn:没有做任何事情。大多数信号运算符仅在订阅结果信号时才起作用。在这种情况下,它只会消失在以太中。
  3. 我们可以立即解决这两个问题。 -addSignalBlock:返回中返回的信号的信号。如果我们返回一些有意义的东西,可以在该方法之外处理。

    首先,需要将其添加到顶部:

    @weakify(self);
    

    如果在下方使用@strongify(self),则会阻止a retain cycle。这是必要的,因为RACCommandself一样长。

    现在,内部信号的创建:

    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];
    

    首先,我们将每个内部信号转换为log,然后忽略错误。 (这有点复杂,但是RAC运营商might be added将来会这样做。)

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

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

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

    每当-performSegueWithIdentifier:sender:发送一个新值(将是从网络返回的arrays)时,这将重新发送NSArray。您可以将其视为随时间调用方法。这是better than a subscription,因为它简化了副作用和内存管理。

答案 1 :(得分:1)

根据我对框架的经验,我发现没有理由直接使用RACSubject,特别是对于像这样的一次性信号。 RACSubjects代表可变信号,在这种情况下您不需要这些信号,实际上可能会增加代码的复杂性。在命令块中返回一个vanilla信号(通过+[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
                }];
            }];
        }];