Lasso 是一种用于构建离散、可组合和可测试组件的 iOS 应用架构,无论大小,从单个一次性屏幕到复杂流程,再到高级应用程序结构。
为什么选择 Lasso?
没有一组结构原则,应用程序的代码库很难进行推理和维护。特别是,以下问题最终会出现:
- 组件之间的紧密耦合使得更改/测试东西变得困难
- 业务逻辑存在于奇怪的地方,使得修改/重用/调试/测试现有代码变得困难
- 视图呈现选择的地点不合适,使得重构/重新组织/测试流程变得困难
- 团队之间的组织不一致,使得跨贡献变得困难
Lasso 方式
Lasso 通过明确定义离散的、单一职责组件以及这些组件之间通信的清晰、灵活方式,鼓励强烈的关注点分离。更大的行为单元可以轻松组合,并可以重新组合。
屏幕
我们通常认为屏幕是一个应用中的单个页面/视图,例如,登录视图、联系人列表视图、音频设置视图等。
在Lasso中,Screen
是一系列类型集合,用于实现单个视图。
View
(即UIViewController
)负责:
- 准确渲染屏幕当前
State
(即内容) - 将用户交互传递给
Store
(即决策者) - 响应状态变化以保持展示的实时性
Lasso视图通常很小,其中几乎不含逻辑。
使用单向数据流
确保一致性:视图从不会直接响应用户操作而重新渲染任何内容。View
向Store
发送Actions
,Store
更新State
,然后作为状态变更通知返回给View
。
Store
是屏幕决策的地方,并且是屏幕State
的真理之源。一个Store
负责:
- 屏幕所有业务逻辑
- 响应
Actions
(即从View
发送的事件) - 屏幕
State
的更新
在发生更适合其他地方处理的事件时,Store
也可以生成一个Output
。例如,登录屏幕可能会生成一个“用户已登录”的输出,作为向高级系统移动到应用登录部分的一个信号;或者联系人列表屏幕可能会生成一个“选择联系人”的输出,作为向高级流程显示或推送联系人详情屏幕的信号。
流程
Flow
代表一个功能——或区域——的应用,通常由一系列Screen
组成。例如,“新用户”流程可能由一系列一次性信息屏幕和一个单独的“开始使用”屏幕组成。
Flow
在视图层次结构适当的上下文中创建和启动(例如,“注册”流程可能在一个菜单屏幕上呈现,或者“欢迎”流程可能被推送到导航堆栈)。Flow
启动时会创建它的初始Screen
,并监听Output
信号。随着Outputs
的到来,Flow
决定如何处理它们——它可以创建并在视图层次结构中放置另一个Screen
,发出它自己的Output
(当发生更适合其他地方处理的事件时),或者对Flow
来说是适当的事情。
由于Screen
和Flow
都被封装成具有离散的进入和退出门户的模块,因此一个Flow
管理Screen
和Flow
都是非常简单和常见的。这样,就可能在组件层次结构中将应用定义为一个结构,从顶层到底层减少复杂性。
示例应用
运行示例项目
- 克隆 Lasso 仓库
- 在
Example
目录下运行pod install
- 打开
Lasso.xcworkspace
了解更多
- Lasso:介绍一个新的 iOS 架构框架 - 文章介绍了 Lasso,并给出了创建
Screen
的具体示例 - Lasso:Flows 简介 - 展示如何使用
Flow
规划多个Screen
- Lasso 编码风格指南 - 写作 Swifty Lasso 的技巧
- 关于声明类型的说明 - 对声明 Lasso 类型更多细节和最佳实践
- 内存管理 - 关于 Lasso 中内存管理的信息以及避免强引用循环的技巧
需求
Lasso 支持 iOS 13 及以上版本,并可以使用 Swift 4.2 及以上版本 进行编译。
注意:Lasso v.1.3.0 添加了对 SwiftUI 的支持,并且具有 iOS 13.0 的最低部署目标。如果需要支持更早的 iOS 版本,请使用 v1.2.1。
安装
Cocoapods
将Lasso核心框架添加到Podfile中的主目标
Pod 'Lasso'
同时将LassoTestUtilities
添加到您的测试目标
Pod 'LassoTestUtilities'
Swift Package Manager
Lasso包的URL为
`https://github.com/ww-tech/lasso.git`
关于示例用法,请参阅: Swift Package Manager 示例。
Tuist
要将Lasso
添加为tuist的TargetDependency
- 将此仓库克隆到您首选的位置
- 创建一个
.project
依赖项 - 将依赖项添加到您的目标依赖项中
let lasso: TargetDependency = .project(target: "Lasso", path: "../../path-to-lasso-repo-clone/")
let demo = Target(
name: "Demo",
platform: .iOS,
product: .framework,
bundleId: "com.example.Demo",
dependencies: [ lasso ]
)
同时将LassoTestUtilities
添加到您的测试目标
let lassoTestUtilities: TargetDependency = .project(target: "LassoTestUtilities", path: "../../path-to-lasso-repo-clone/")
let demoTests = Target(
name: "DemoTests",
platform: .iOS,
product: .unitTests,
bundleId: "com.example.DemoTests",
dependencies: [ LassoTestUtilities ]
)
贡献
我们热爱贡献!
如果您有新功能的想法,或者发现了bug,最好的做法是
- 搜索问题,看看是否有人已经提出过!
- 创建一个新问题,详细说明您希望看到哪些改进。
- 如果您有代码更改的想法,那就太棒了!
- Fork Lasso仓库
- 为您的功能更改创建一个分支
- 开启一个PR!
作者
Steven Grosmark,Trevor Beasty,Yuichi Kuroda,以及WW iOS团队。
许可证
Lasso遵循Apache-2.0开源许可证。
您可以自由使用它。我们欢迎您给予归功(致谢),如果您在使用项目中使用它,我们非常乐意听到您的反馈!