热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

swift引入pod_引入自旋:Swift中的通用反馈循环系统

swift引入pod快速应用中对架构模式的需求随着最近对Combine和SwiftUI的介绍,我们将在代码库中面临一些过渡期。我们的应用程序将使用Combine和第三

swift 引入pod

快速应用中对架构模式的需求

随着最近对Combine和SwiftUI的介绍,我们将在代码库中面临一些过渡期。 我们的应用程序将使用Combine和第三方响应框架,或者同时使用UIKit / AppKit和SwiftUI。 随着时间的推移,这可能难以保证一致的体系结构。 很难知道何时将这些新技术结合到我们的项目中。 从一开始就正确选择体系结构可能会大大简化将来的过渡。

传统的架构模式(例如MVC , MVP或MVVM)大多负责UI层。 当以统一的方式在您的应用程序内部混合上述技术时,它们不会有太大帮助。 例如,UIKit应用程序中的MVVM将在很大程度上依赖双向绑定技术,并具有React性扩展,例如RxCocoa或ReactiveCocoa。 随着您逐步引入SwiftUI和Combine,这将变得不那么正确。 很有可能会在您的应用程序中包含多个架构范例。

VIPER更加完整,因为它描述了场景之间的路由机制以及模型(实体)与管理它们的业务规则(交互器)之间的分离。 这种模式执行了关于关注点和依赖关系管理分离的“ 干净架构”的原则。 但是,与MVxx模式一样,当逐步采用Combine或SwiftUI时,它也无法保证语法和范例在时间上的一致性。

SwiftUI将状态视为事实的唯一来源,并对状态突变做出React。 这允许以声明的方式而不是冗长且易于出错的命令性方式来编写视图。

近年来,围绕状态概念出现了几种架构模式: Redux或MVI之类的东西以及更普遍的单向数据流架构。 有时他们会提出以中央方式(有时是局部方式)来管理国家。 这些都是很好的模式,非常适合将国家作为唯一真理来源的思想。 我敢肯定,他们在SwiftUI的制作中具有很大的影响力。

我本人已经在生产应用程序中实现了其中一些模式。 他们向我介绍了函数式编程,因为它们依赖于不变性,纯函数,函数组成等概念。 功能编程和状态管理非常吻合。 功能编程与数据不变性相关,因此状态也应如此。

但是,我遇到了这类架构的一些缺点,这使我发现了反馈回路系统。

什么是反馈回路系统?

反馈回路是一种系统,能够通过将其计算的结果值用作自身的下一个输入来进行自我调节,并根据给定规则不断调整该值(反馈回路用于电子等领域,以自动调整电平例如信号)。

声明这种方式听起来可能模糊不清,并且与软件工程无关, 但是 “根据某些规则调整值”正是为程序以及扩展为应用程序而设计的! 应用程序是我们要调节的各种状态的总和,以便遵循精确的规则提供一致的行为。

状态机描述描述从一个值到另一个值的允许过渡的规则。

什么是状态机?

它是一台抽象机,在任何给定时间都可以恰好处于有限数量的状态之一。 状态机可以响应某些外部输入而从一种状态变为另一种状态。 从一种状态到另一种状态的转换称为过渡。 状态机由其状态,其初始状态以及每个转换的条件的列表定义。

由于应用程序是状态机,因此它可以由反馈回路系统驱动。

反馈环基于三个组件:初始状态,反馈和减速器。 为了说明它们中的每一个,我们将依靠一个基本示例:一个系统,计数范围从0到10。

  1. 初始状态 :这是计数器的初始值0。
  2. 反馈 &#xff1a;这是我们应用于柜台以实现目标的规则。 反馈的输出是对计数器进行更改的请求。 如果0 <&#61; counter <10&#xff0c;那么我们要求增加它&#xff0c;否则我们要求停止它。
  3. 减速器 &#xff1a;这是我们系统的状态机。 它描述了给定其先前值的所有可能的计数器转换以及由反馈计算出的请求。 例如&#xff1a;如果先前的值为0&#xff0c;并且请求增加该值&#xff0c;则新的值为1&#xff1b; 如果前一个是1&#xff0c;并且请求增加它&#xff0c;那么新值是2&#xff1b; 等等等等。 当来自反馈的请求停止时&#xff0c;则将先前的值作为新值返回。

反馈是唯一可以执行副作用&#xff08;网络&#xff0c;本地I / O&#xff0c;UI呈现&#xff0c;无论您执行什么操作来访问或更改循环本地范围之外的状态&#xff09;的地方。 相反&#xff0c;reduceer是一个纯函数&#xff0c;仅在给定前一个值和转换请求的情况下才可以产生新值。 禁止在还原剂中产生副作用&#xff0c;因为这会损害其再现性。

传统单向数据流体系结构的缺点

如您所见&#xff0c;反馈回路范式的特殊之处在于&#xff0c;状态既是输入又是输出&#xff0c;两者都作为一个整体连接在一起形成一个单向回路。 只要循环是活动的并且正在运行&#xff0c;我们就可以将状态用作本地缓存来持久化数据。

典型的用例是浏览分页的API。 这种系统允许使用当前状态来始终使上一页URL和下一页URL可以访问&#xff0c;这与将其存储在其他位置无关。

在更传统的单向数据流体系结构中&#xff0c;状态仅是系统的输出。 输入是触发副作用然后声明突变的“用户意图”。

我经历过几种类型的体系结构&#xff08;Redux和MVI&#xff09;&#xff0c;发现自己陷入了两个主要问题&#xff1a;

  1. 不使用状态作为输入会导致在UI层或缓存存储库中维护本地状态。
  2. 依靠经常是枚举的诸如Intent或Action之类的输入&#xff0c;迫使我们使用&#96;switch&#96;语句对它们进行解析&#xff0c;以确定要执行的副作用。 当我们添加新的意图或新动作时&#xff0c;我们必须改变我们解析它们的方式&#xff0c;这违反了SOLID规则的“打开/关闭”原则。

我并不是说这些问题是使用这些体系结构的“不可行”&#xff0c;也不是说我已经以最好的方式使用了它们。 例如&#xff0c;我研究了MVI的一种变体&#xff0c;其中用“命令模式”代替了意图。 每个命令负责其自己的副作用执行。 没有解析&#xff0c;命令是自给自足的。 这种方法符合“打开/关闭”原则&#xff0c;因为添加新功能就是添加要执行的新命令。 而不修改意图或动作的解析方式。

但是&#xff0c;我赞成采用一种自然解决这些问题的方法&#xff0c;而不是为了满足我的需要而扭曲这些体系结构&#xff1a;反馈回路系统。

什么是自旋&#xff1f;

让我们回到我们的主要关注点&#xff1a;提供一种架构模型&#xff0c;该模型可以吸收我们在应用程序中可以期望的技术差异。

如我们所见&#xff0c;反馈循环是一种非常通用的模式。 这将帮助我们减轻混合技术的影响。 但是我们需要一种方法来以统一的方式声明反馈循环&#xff0c;而不管底层的React式框架或所选的UI技术如何。 这就是Spin发挥作用的地方。

Spin 是在基于Swift的应用程序中构建反馈循环的工具&#xff0c;无论您使用底层的React式编程框架还是使用任何Apple UI技术&#xff08;RxSwift&#xff0c;ReactiveSwift&#xff0c;Combin和UIKit&#xff0c;AppKit&#xff0c;SwiftUI&#xff09;&#xff0c;都可以使用统一语法。

让我们尝试通过构建一个调节两个整数以使其收敛到平均值的系统来旋转 &#xff08;就像某种系统可以调整立体声扬声器的左右音频通道以使其收敛到相同的电平一样&#xff09;。

我们需要一个用于状态的数据类型&#xff1a;

struct Levels {let left : Intlet right : Int
}

我们还将需要一种数据类型来描述要在Levels上执行的转换&#xff1a;

enum Event {case increaseLeftcase decreaseLeft case increaseRightcase decreaseRight
}

为了描述状态机控制过渡的过程&#xff0c;我们需要一个reducer函数&#xff1a;

func levelsReducer (currentLevels: Levels, event: Event) -> Levels {guard currentLevels. left !&#61; currentLevels. right else { return currentLevels }switch event {case .decreaseLeft:return Levels ( left : currentLevels. left - 1 , right : currentLevels. right )case .increaseLeft:return Levels ( left : currentLevels. left &#43; 1 , right : currentLevels. right )case .decreaseRight:return Levels ( left : currentLevels. left , right : currentLevels. right - 1 )case .increaseRight:return Levels ( left : currentLevels. left , right : currentLevels. right &#43; 1 )}
}

到目前为止&#xff0c;代码与特定的React式框架无关&#xff0c;这很棒。

让我们写两个对每个级别都有影响的反馈。

使用RxSwift &#xff1a;

func leftEffect (inputLevels: Levels) -> Observable {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return .empty() }// this is the regulation for the left levelif inputLevels. left }func rightEffect (inputLevels: Levels) -> Observable {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return .empty() }// this is the regulation for the right levelif inputLevels. right }

使用ReactiveSwift &#xff1a;

func leftEffect (inputLevels: Levels) -> SignalProducer {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return .empty }// this is the regulation for the left levelif inputLevels. left }func rightEffect (inputLevels: Levels) -> SignalProducer {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return .empty }// this is the regulation for the right levelif inputLevels. right }

合并 &#xff1a;

func leftEffect (inputLevels: Levels) -> AnyPublisher {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return Empty ().eraseToAnyPublisher() }// this is the regulation for the left levelif inputLevels. left }func rightEffect (inputLevels: Levels) -> AnyPublisher {// this is the stop condition to our Spinguard inputLevels. left !&#61; inputLevels. right else { return Empty ().eraseToAnyPublisher() }// this is the regulation for the right levelif inputLevels. right }

无论您选择哪种React技术&#xff0c;编写反馈循环&#xff08;也称为Spin &#xff09;都非常简单&#xff1a;

let levelsSpin &#61; Spinner.initialState( Levels ( left : 10 , right : 20 )).feedback( Feedback (effect: leftEffect)).feedback( Feedback (effect: rightEffect)).reducer( Reducer (levelsReducer))

而已。 您可以在应用程序的一部分中使用RxSwift&#xff0c;在另一应用程序中使用Combine&#xff0c;所有反馈循环将使用相同的语法。

对于“类似于DSL”的语法爱好者&#xff0c;还有一种更具声明性的方式&#xff1a;

let levelsSpin &#61; Spin (initialState: Levels ( left : 10 , right : 20 ), reducer: Reducer (levelsReducer)) {Feedback (effect: leftEffect)Feedback (effect: rightEffect)
}

如何开始循环&#xff1f;

// With RxSwift
Observable.start(spin: levelsSpin).disposed(by: disposeBag)// With ReactiveSwift
SignalProducer.start(spin: levelsSpin).disposed(by: disposeBag)// With Combine
AnyPublisher.start(spin: levelsSpin).store( in : &cancellables)

混合React框架不再是问题issue。

在UI透视图中使用Spin

尽管反馈循环本身可以不存在任何可视化而存在&#xff0c;但在我们的开发人员世界中&#xff0c;将其用作产生将在屏幕上呈现的State的方式以及处理用户发出的事件的方式更有意义。

幸运的是&#xff0c;将状态作为输入来呈现和从用户交互返回事件流看起来就像是反馈的定义&#xff0c;并且我们知道如何处理反馈&#x1f601;&#xff0c;当然是自旋的。

一旦构建了Spin / feedback循环&#xff0c;我们就可以使用专门用于UI渲染/交互的新反馈来“修饰”它。 存在一种特殊的Spin来执行这种装饰&#xff1a;UISpin。

作为全局图片&#xff0c;我们可以使用此图说明UI上下文中的反馈循环&#xff1a;

在ViewController中&#xff0c;假设您有一个类似的渲染功能&#xff1a;

func render (state: State) {switch state {case .increasing( let value):self .counterLabel.text &#61; "\(value)"self .counterLabel.textColor &#61; .greencase .decreasing( let value):self .counterLabel.text &#61; "\(value)"self .counterLabel.textColor &#61; .red}
}

我们需要用UISpin装饰“商务”旋转&#xff08;例如&#xff0c;在viewDidLoad函数中&#xff09;。

// previously defined or injected: counterSpin is the Spin that handles our counter rules
self .uiSpin &#61; UISpin (spin: counterSpin)
// self.uiSpin is now able to handle UI side effects
// we now want to attach the UI Spin to the rendering function of the ViewController:
self .uiSpin.render(on: self , using: { $ 0 .render(state:) })
// And once the view is ready (in “viewDidLoad” function for instance) let’s start the loop:
self .uiSpin.start()
// the underlying reactive stream will be disposed once the uiSpin will be deinit

在循环中发送事件非常简单&#xff1b; 只需使用发出功能&#xff1a;

self .uiSpin.emit( Event .startCounter)

那么SwiftUI呢&#xff1f;

由于SwiftUI依赖于状态和视图之间的绑定的思想并负责渲染&#xff0c;因此连接SwiftUI Spin的方式略有不同&#xff0c;甚至更简单。

在您看来&#xff0c;您必须使用“ &#64;ObservedObject”注释SwiftUI Spin变量&#xff1a;

&#64;ObservedObject
private var uiSpin: SwiftUISpin &#61; {// previously defined or injected: counterSpin is the Spin that handles our counter businesslet spin &#61; SwiftUISpin (spin: counterSpin)spin.start()return spin
}()

然后&#xff0c;您可以在视图内使用“ uiSpin.state”属性显示数据&#xff0c;并使用uiSpin.emit&#xff08;&#xff09;发送事件。 由于SwiftUISpin也是一个“ ObservableObject”&#xff0c;因此每个状态突变都会触发视图渲染。

Button (action: {self .uiSpin.emit( Event .startCounter)
}) {Text ( "\(self.uiSpin.state.isCounterPaused ? " Start ": " Stop ")" )
}// A SwiftUISpin can also be used to produce SwiftUI bindings:
Toggle (isOn: self .uiSpin.binding( for : \.isPaused, event: .toggle) {Text ( "toggle" )
}// \.isPaused is a keypath which designates a sub state of the state,
// and .toggle is the event to emit when the Toggle is changed.

UIKit&#xff08;AppKit&#xff09;和SwiftUI使用UISpin的方式非常相似&#xff0c;允许您将先前为UIKit屏幕编写的反馈循环集成到新的SwiftUI组件中。

混合UI范例不再是问题&#x1f44d;。

结论

我们已经达到了目标&#xff1a;提出一种架构模式实现&#xff0c;可以简化新技术之间的过渡。

在Spinners组织中&#xff0c;您可以找到2个演示应用程序&#xff0c;这些应用程序演示了如何将Spin与RxSwift&#xff0c;ReactiveSwift和Combine结合使用。

  1. 基本的计数器应用程序&#xff1a; UIKit版本和SwiftUI版本
  2. 使用依赖项注入和协调器模式&#xff08;UIKit&#xff09;的更高级的“基于网络”应用程序&#xff1a; UIKit版本和SwiftUI版本

有Spin.Swift回购 。 公关当然是受欢迎的&#xff08;例如⭐️&#x1f60f;&#xff09;。

我打算开发与RxJava和Flow兼容的Kotlin实现&#xff08;不胜感激&#xff09;。

希望您喜欢这篇文章。 随时发表评论&#xff0c;以便我们进行交流。




翻译自: https://hackernoon.com/introducing-spin-a-universal-feedback-loop-system-in-swift-rg2i3yab

swift 引入pod



推荐阅读
  • 我在尝试将组合框转换为具有自动完成功能时遇到了一个问题,即页面上的列表框也被转换成了自动完成下拉框,而不是保持原有的多选列表框形式。 ... [详细]
  • 本文详细介绍了在PHP中如何获取和处理HTTP头部信息,包括通过cURL获取请求头信息、使用header函数发送响应头以及获取客户端HTTP头部的方法。同时,还探讨了PHP中$_SERVER变量的使用,以获取客户端和服务器的相关信息。 ... [详细]
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • Flutter 核心技术与混合开发模式深入解析
    本文深入探讨了 Flutter 的核心技术,特别是其混合开发模式,包括统一管理模式和三端分离模式,以及混合栈原理。通过对比不同模式的优缺点,帮助开发者选择最适合项目的混合开发策略。 ... [详细]
  • 使用jQuery与百度地图API实现地址转经纬度功能
    本文详细介绍了如何利用jQuery和百度地图API将地址转换为经纬度,包括申请API密钥、页面构建及核心代码实现。 ... [详细]
  • 本文深入探讨了Linux内核中进程地址空间的设计与实现,包括虚拟地址空间的概念、内存描述符`mm_struct`的作用、内核线程与用户进程的区别、进程地址空间的分配方法、虚拟内存区域(VMA)的结构以及地址空间与页表之间的映射机制。 ... [详细]
  • 函子(Functor)是函数式编程中的一个重要概念,它不仅是一个特殊的容器,还提供了一种优雅的方式来处理值和函数。本文将详细介绍函子的基本概念及其在函数式编程中的应用,包括如何通过函子控制副作用、处理异常以及进行异步操作。 ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • HTML前端开发:UINavigationController与页面间数据传递详解
    本文详细介绍了如何在HTML前端开发中利用UINavigationController进行页面管理和数据传递,适合初学者和有一定基础的开发者学习。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本文总结了软件工程课程M1和M2阶段的个人收获,包括项目开发中的技术学习、团队协作及管理经验。同时,对《构建之法》一书中的相关问题进行了理解和分析。 ... [详细]
  • ABP框架是ASP.NET Boilerplate的简称,它不仅是一个开源且文档丰富的应用程序框架,还提供了一套基于领域驱动设计(DDD)的最佳实践架构模型。本文将详细介绍ABP框架的特点、项目结构及其在Web API优先架构中的应用。 ... [详细]
  • IA64架构下常见编程陷阱探讨
    本文深入探讨了IA64架构中常见的一个编程错误案例,该案例揭示了当开发者试图绕过编译器的某些限制时可能遇到的问题。通过具体分析IA64架构的特点及其对全局变量处理的方式,本文旨在为开发者提供避免此类问题的有效建议。 ... [详细]
  • 本文详细介绍了在Windows系统中如何配置Nginx以实现高效的缓存加速功能,包括关键的配置文件设置和示例代码。 ... [详细]
author-avatar
文女2010_532
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有