使用 MVVM 和 ReactiveCocoa,如何处理 iOS 中的委托模式?

With MVVM and ReactiveCocoa, how to handle the delegate pattern in iOS?

一个常见的情况是有一个View Controller A,它有一些信息会被发送到View Controller B;而B会编辑信息,当B编辑完信息后,B会调用delegate方法更新A,并从navigation controller中pop自身

如何使用 MVVM 和 ReactiveCocoa 处理这个问题?

一般来说,大量使用 ReactiveCocoa 会使您远离委托模式。但是,由于您已经编写的大部分代码以及您将在 iOS 标准库中遇到的所有代码都使用它,因此能够与它交互仍然很重要。

您需要使用 -[NSObject rac_signalForSelector:] 类别,这将 return 一个信号,该信号在每次调用方法时接收 RACTuple 参数值,并且当发送信号的对象被释放时完成。

假设您有一个要显示的 UIViewController,其中包含用户可以 select 的复选框列表,底部有一个继续按钮。由于 select 离子随时间变化,您可以将其表示为 RACSignalNSIndexSet 值。出于本示例的目的,假设您必须按原样使用此 class,并且它当前声明了一个包含以下内容的委托模式:

@class BSSelectionListViewController;
@protocol BSSelectionListViewControllerDelegate <NSObject>
   - (void)listChangedSelections:(BSSelectionListViewController*)list;
   - (void)listContinueTouched:(BSSelectionListViewController*)list;
@end

当您从其他地方(如导航堆栈顶部的 UIViewController)显示视图控制器时,您将创建视图控制器并将 self 指定为委托。它可能看起来像

BSSelectionListViewController* listVC = [[BSSelectionListViewController alloc] initWithQuestion:question listChoices:choices selections:idxSet];
    listVC.delegate = self;
[self.navigationController pushViewController:listVC];

在将此 UIViewController 压入堆栈之前,您需要为它可以调用的委托方法创建信号:

RACSignal* continueTouched = [[[self rac_signalForSelector:@selector(listContinueTouched:)]
                                  takeUntil:list.rac_willDeallocSignal]
                                  filter:^BOOL(RACTuple* vcTuple)
    {
        return vcTuple.first == listVC;
    }];

    RACSignal* selections = [[[[self rac_signalForSelector:@selector(listChangedSelections:)]
                              takeUntil:list.rac_willDeallocSignal]
                              filter:^BOOL(RACTuple* vcTuple)
    {
        return vcTuple.first == listVC;
    }]
                             map:^id(RACTuple* vcTuple)
    {
        return [vcTuple.first selections];
    }];

然后您可以订阅这些信号来执行您需要的任何副作用。也许是这样的:

RAC(self, firstChoiceSelected) = [selections map:^id(NSIndexSet* selections)
    {
        return @([selections containsIndex:0]);
    }];

@weakify(self)
[continueTouched subscribeNext:^(id x)
{
    @strongify(self)
    [self.navigationController popToViewController:self];
}];

因为您可能有几个这样的屏幕是您的代理人,所以您要确保在 RACSignals 中只过滤到这个屏幕。

ReactiveCocoa 实际上会为你实现这些方法(委托协议中的方法)。但是,为了让编译器满意,您应该添加存根。

- (void)listChangedSelections:(BSSelectionListViewController *)list {}
- (void)listContinueTouched:(BSSelectionListViewController*)list {}

IMO,这是对标准委托模式的改进,在标准委托模式中,您需要声明一个实例变量来保存 selection 视图控制器,并检查控制器调用您的委托方法. ReactiveCocoa 的 rac_signalForSelector 方法可以将该状态的范围(这个视图控制器随时间来来去去)缩小到局部变量而不是实例变量。它还允许您明确处理 select 离子的变化。