ReactiveSwift简介

嗨,我是Patrick-Mercari的iOS工程师。 在Mercari,我们的客户工程团队使用反应式编程范例。 近年来,这种范例已经发展并在开发社区中变得越来越流行。 在本文中,我想简要介绍一下使用ReactiveSwift的反应式编程。 为了演示这些概念,我在本文旁边提供了一个示例项目供您参考。 当按下按钮时,它只是显示随机的颜色,但是它说明了如何使用反应式编程。 请注意,本文假定您已熟练掌握Swift。

在Mercari以及此示例项目中,我们将Model-View-ViewModel(MVVM)架构与ReactiveSwift一起使用。 ReactiveSwift库使我们可以更轻松地应用反应式编程范例。 反应式编程的核心是的概念—流只是发送值,当我们观察它们时,我们可以采取行动。 在ReactiveSwift中,流的这一概念由Signal类表示。 我们可以将用户交互建模为信号。 这与我们选择的架构MVVM完美匹配。

ViewModel是MVVM最重要的部分之一。 必须执行的任何逻辑都应存在于ViewModel内部。 这显然将整个应用程序中的职责分开:ViewModels处理逻辑,而视图和ViewControllers根据此逻辑的输出进行更新。 在ViewModel层中将这种模式表示为输入和输出很容易。 这是一个大致的视图:

输入项

您可以将输入视为已执行的操作,无论是由ViewController本身( viewDidLoad )还是由用户(轻击和轻扫)执行; 我们希望对他们采取行动以执行一些工作。 在示例项目中,我们可以看到如何捕获输入。 如下所示, RandomColorViewModelInputs协议声明执行某些操作时将调用的函数:

 协议RandomColorViewModelInputs { 
func viewDidLoad()
func newColorButtonTapped()
}

例如,当viewDidLoad方法在我们的ViewController内部触发时,将调用viewDidLoad() 。 我们可以使用ReactiveSwift的.pipe()使这些函数在实现内部具有反应性。 这使我们能够创建一个Signal ,我们可以通过该Signal发送输入并观察结果输出。 在这里,我们可以看到viewDidLoadIO的定义(我们的管道,用于输入/输出的IO )和提供输入的viewDidLoad() (来自我们的协议):

 私人让viewDidLoadIO = Signal  .pipe() 
func viewDidLoad(){
viewDidLoadIO.input.send(value:())
}

viewDidLoadIO是使用.pipe()的信号。 它发送一个Void值,并具有NoError作为关联的错误类型(本质上意味着它永远不会出错)。 在函数viewDidLoad()我们可以看到Void输入正在通过viewDidLoadIO发送。 这是通过viewDidLoadIO的整体流程的外观:

产出

输出是采取措施的结果。 例如,如果调用了viewDidLoad ,我们想生成一个随机颜色并将其显示给用户。 在此ViewModel中仅定义了一个输出。 在我们的输出协议中,我们可以看到单个信号的声明displayModelSignal 。 它发送一个结构,该结构包含有关我们的视图外观的信息:

 协议RandomColorViewModelOutputs { 
var displayModelSignal = Signal {get}
}

首次加载视图时,我们想对其进行配置。 那就是上面的viewDidLoadIO进入的地方viewDidLoadIO只是一个信号-它是input管道的另一端。 我们可以转换该信号以创建displayModelSignal ,当观察到该模型时,它将为我们的UI提供更新。 不要让这个吓到你。 我们将在下一部分中将其逐步分解:

  var displayModelSignal:Signal  { 
返回信号
。合并(
viewDidLoadIO.output,
newColorButtonTappedIO.output

.map {RandomColorDisplayModel()}
}

需要注意的重要一点是,通过使用viewDidLoadIO.output信号,可以在viewDidLoad()触发时观察Void的值。 Void值不是很有帮助,但是在ReactiveSwift中,我们可以使用运算符来转换信号发送的值。 在这种情况下, Void将被映射到RandomColorDisplayModel结构。

在上面的示例中,我们可以看到计算出的属性displayModelSignal —这只是在RandomColorViewModelOutputs协议中声明的信号的实现。 返回的信号由两个运算符组成: mergemap 。 它看起来有点复杂,但让我们分解一下:

 信号 
。合并(
viewDidLoadIO.output,
newColorButtonTappedIO.output

Signal.merge()接受我们提供的任何信号,并将它们合并为一个信号。 当viewDidLoadIO.outputnewColorButtonTappedIO.output发送Void时,此新信号将发送Void 。 这也意味着在合并声明中,我们的信号产生的值必须相同。 这将我们带到map运算符:

  .map {RandomColorDisplayModel()} 

就像集合一样, map允许我们转换信号发送的每个值。 在这种情况下,这非常简单-当我们的合并信号发送一个值( Void )时,我们将返回一个新初始化的RandomColorDisplayModel 。 更复杂的示例可能需要根据发送的值进行转换。

现在,我们在ViewModel中具有输入和输出。 我们只需要在ViewController内部发送输入并观察输出,就可以在RandomColorViewController内部RandomColorViewController 。 顶部有一个viewModel的声明(使用我们的RandomColorViewModel ):

 最后的课程RandomColorViewController:UIViewController { 
让viewModel = RandomColorViewModel()
}

使用此ViewModel,我们可以捕获输入。 为了创建输出,必须有输入。 因此,当采取措施时,我们要调用相应的输入法。 在viewDidLoad内部,我们调用viewModel.input.viewDidLoad()方法。 如上所述,这将通过我们的viewDidLoadIO管道发送一个Void类型的值:

 覆盖func viewDidLoad(){ 
super.viewDidLoad()bindViewModel()
viewModel.inputs.viewDidLoad()
}

现在,我们要做的就是观察这些变化,上面的代码片段中的bindViewModel()就会出现。此方法使我们可以配置对输出的观察以及基于该输出采取的后续操作。 可以使用.observeValues观察ViewModel的所有输出信号。 这使我们可以创建一个闭包,该闭包具有在信号内部发送的值的参数。 闭包将这些值作为参数并执行更新:

 私人功能bindViewModel(){ 
viewModel.outputs.displayModelSignal.observeValues {[弱自我]在
self?.colorDescriptionLabel.text = $ 0.description
self..colorView.backgroundColor = $ 0.color
}
}

由于在这种封闭状态下正在进行自我工作,因此我们应该弱化自我-因此[weak self] 。 还要注意, $0是一个匿名闭包参数—在这种情况下,它代表RandomColorDisplayModel结构。 考虑到这些细节,我们的闭包主体简单地采用信号发送的结构并将其应用于ViewController。 使用RandomColorDisplayModel上的值设置背景颜色和标签文本。 每次通过我们的displayModelSignal发送值时都会执行此过程。

尽管这是一个非常简单的示例,但它演示了反应式编程的应用。 它应用了严格定义且可重复的模式。 UI更新在中央位置进行了干净的建模和处理。 当然,随着UI复杂性的提高,将displayModelSignal分解为更小,更易于管理的部分将是有意义的。 我希望本文能启发您尝试一下。 谢谢阅读!