使用 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 离子随时间变化,您可以将其表示为 RACSignal
的 NSIndexSet
值。出于本示例的目的,假设您必须按原样使用此 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 离子的变化。
一个常见的情况是有一个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 离子随时间变化,您可以将其表示为 RACSignal
的 NSIndexSet
值。出于本示例的目的,假设您必须按原样使用此 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 离子的变化。