RACSignal和一系列视图模型,是否存在更具反应性的方式?

时间:2015-02-03 19:19:06

标签: ios objective-c mvvm reactive-cocoa

我使用UITableViewController来显示已发现的设备。它们是从SDDeviceBrowser获得的,它会不断扫描它们,并在每次找到新设备时调用其委托方法。我像这样创建了RACSignal

@implementation SDDeviceBrowser (RAC)
- (RACSignal*)rac_newDeviceSignal {
    return [self rac_signalForSelector:@selector(deviceBrowserDidFindNewDevice:) fromProtocol:@protocol(SDDeviceBrowserDelegate)];
}
@end

我使用MVVM模式:我的表视图的视图模型是DeviceListViewModel。它有一个数组devices,包含绑定到表视图单元格的子视图模型。它映射浏览器的信号并将其公开给视图控制器:

@interface DeviceListViewModel ()
@property (strong, readwrite, nonatomic) NSArray *devices;
@property (strong, nonatomic) SDDeviceBrowser *browser;
@end

//boring initialization ommitted

- (RACSignal *)deviceFoundSignal {
    return [[self.browser rac_newDeviceSignal] map:^id(RACTuple* parameters) {
      SDDevice *device = parameters.last;
      DeviceViewModel *deviceViewModel = [[DeviceViewModel alloc] initWithDevice:device];
      self.devices = [self.devices arrayByAddingObject:deviceViewModel];
      return deviceViewModel;
    }];
}

然后表视图控制器订阅deviceFoundSignal并在找到新设备时插入一行:

[[self.viewModel.deviceFoundSignal throttle:0.5] subscribeNext:^(id value) {
  [self.refreshControl endRefreshing];
  //insert new rows to the table view inside beginUpdates/endUpdates 
}];

还可以“重置”设备浏览器:它清除已发现设备的列表并再次开始扫描。但是,我找不到一个很好的反应解决方案来处理 - 我只是执行以下操作(在视图控制器中):

[[self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged] subscribeNext:^(id x) {
  [self.viewModel restartScanning];  //clears the 'devices' array and restarts the browser
  [self.tableView reloadData];
}];

这有效,但我认为可以采用更“反应”的方式。将新视图模型添加到map:块内的数组看起来有点难看。我是否遗漏了可以在这里使用的ReactiveCocoa的任何功能?

1 个答案:

答案 0 :(得分:1)

要组装设备阵列,请查看-scanWithStart:reduce:。使用此方法,您可以从一个空数组开始,并让reduce块将每个设备添加到数组中。例如:

[[[self.browser
    rac_newDeviceSignal]
    map:^(RACTuple *parameters) {
        SDDevice *device = parameters.last;
        return [[DeviceViewModel alloc] initWithDevice:device];
    }]
    scanWithStart:@[] reduce:^(NSArray models, DeviceViewModel *deviceViewModel) {
        return [devices arrayByAddingObject:deviceViewModel];
    }]

对于" reset"这并没有太大作用。功能。结合"添加"和"重置"在一个信号和一个扫描中,我会做以下事情:

首先,在代码中找到可以暴露两个相关信号的地方,即-rac_newDeviceSignal,哪个信号代表导致重置的事件。我将后一个信号称为" resetSignal"。

有了设备添加信号和重置信号,我会将它们各自映射到"操作"在设备阵列上。我的意思是"操作",基本上是一个接受旧设备阵列的块,并返回一个新的设备数组。

RACSignal *addOperation = [[self.browser rac_newDeviceSignal] map:^(RACTuple *parameters) {
    return ^(NSArray *devices) {
        SDDevice *device = parameters.last;
        DeviceViewModel *model = [[DeviceViewModel alloc] initWithDevice:device];
        return [devices arrayByAddingObject:model];
    };
}]

RACSignal *resetOperation = [resetSignal map:^(id _) {
    return ^(NSArray *devices) {
        return @[];
    };
}]

有了这两个信号,它们就可以+merge:成为一个信号,然后可以像上面显示的那样进行扫描。

[[RACSignal
    merge:@[ addOperation, resetOperation ]]
    scanWithStart:@[] reduce:(NSArray *devices, NSArray *(^operation)(NSArray *)) {
        return operation(devices);
    }]