引言
最近在自研一个新的项目,在考虑使用的技术栈时,调研了许多,比如react-native,flutter,以及端原生的oc跟swift,但是最终选择了swiftUI + combine,之所以有如此决定,一方面是希望可以完善自己对于iOS系统开发的技术完整性,另一方面希望了解iOS开发未来的一个技术方向,那么闲言少叙切入正题。什么是swiftUI?
SwiftUI是什么?
更准确地解释可以移步到苹果开发者中心,概念性的东西,这里不做过多介绍。通过对其的一段时间开发,个人总结,swiftUI绝不是swift+UI这么简单的概念,从设计上,swiftUI十分趋近于web前端,苹果似乎有意将swift做得更加简化,swiftUI也是将开发者得注意力从之前无穷尽地修改UI转到更加关注其app内部的逻辑处理。
简而言之,如果你的项目需求崇尚极简主义,注重逻辑而不采用复杂且臃肿的交互设计,那么swiftUI绝对是值得一试的技术手段。
对于swiftUI的各个组件,官方都给出的事例,这里先不做研究,之后我会在自研项目上线之后,对于其中所用到的组件,遇到的问题,进行逐步汇总,其中会有一些在国内论坛并不容易找到的问题答案。但是现在我们先从基础数据入手,我们先了解一下什么是combine。
如何理解combine
谈到combine
不得不提的就是swift中的属性修饰器-- @propertyWrapper:
@propertyWrapper
实话实说,如果你还没有用过propertyWrapper,那一定要尝试的使用一下,因为这个功能确实太好用了,这里引用官方解释的一段话:
For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.
塑料翻译:
例如,如果你要为数据存储的一些基础属性提供线程安全或者存储它们,你不得不在每一个属性中都写同样的方法,这会让代码变得十分恶心。但是当你使用propertyWrapper时,当你为操作代码定义了一个修饰器,那么这些操作代码会应用在它修饰的多个属性中。
上面的解释,是我在学习propertyWrapper所能看到的最为通俗的解释。下面也是提供了一段官方代码,帮助理解。
@propertyWrapper struct TwelveOrLess { private var number = 0 var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } } struct SmallRectangle { @TwelveOrLess var height: Int @TwelveOrLess var width: Int } var rectangle = SmallRectangle() print(rectangle.height) // Prints "0" rectangle.height = 10 print(rectangle.height) // Prints "10" rectangle.height = 24 print(rectangle.height) // Prints "12"
简单解释一下上面的代码,声明一个属性修饰器TwelveOrLess,内部的逻辑是输出的属性都比12小,如果大于12则输出12。
下面的SmallRectangle包装了两个属性height与width,当我们为这两个属性赋值,再调用get方法时,可以看到,我们的逻辑代码生效了,输出数字被控制在小于或等于12的值。
无需多余代码,属性修饰器给了swift开发者更多的想象空间。
简单的介绍了一下propertyWrapper,接下来我们回归正题,继续说回combine。
Publishers 与 subscribers
如果想使用combine就不得不了解两个概念,Publishers 与 subscribers。如果你之前有做过Rxswift,或者对于RAC有一定了解的话,对于这两个概念一定不陌生。即便是对于上述框架并不了解,想要理解Publishers 与 subscribers也不难,因为可以把它理解为观察者模式中的发送者与监听者。
由于官方的事例采用的是通知中心的demo,这在我初学combine时给我带来了极大的困扰,因此,本文的事例并不打算采用官方事例,避免给读者带来同样的困扰。而是通过一段自己的部分开源代码对其进行讲解。
struct XXAssetModel{ var id = UUID() var currency: Int } class XXResourceViewModel: ObservableObject { @Published var myAsset: XXAssetModel = UserData.userCurrency fileprivate func editCurrency() { myAsset.currency = myAsset.currency + 10 } } struct ConverterView : View { @ObservedObject var viewModel = XXResourceViewModel() var body: some View { return Text(viewModel.myAsset.currency) } }
这个例子相对简单,便于入门,我们来看一下,首先,在XXResourceViewModel中声明一个被 @Published修饰的属性myAsset,因为我们刚刚已经介绍过属性修饰器了,所以应该不难理解这个修饰的作用。下面引用官方的一段话。
Add the @Published annotation to a property of one of your own types. In doing so, the property gains a publisher that emits an event whenever the property’s value changes.
将 @Published 注释添加到类中的属性。这样做使该属性成为了一个publisher,只要该属性的值发生变化,publisher就会发出一个事件。
回到上面一段代码,publisher就像是电影《风声》中的老鬼,他的责任就是将自己获取的情报传递给他的上级老枪,那么,谁是subscribers老枪。上例中,Text控件就是老枪。他与viewModel.myAsset.currency
形成了一种绑定关系,一旦viewModel.myAsset.currency
发生改变,Text接收到信号之后,就会做出对应行动。
看到这有没有人在想到了一种设计模式?没错,就是MVVM。
Subject的使用
combine作为苹果官方推出的响应式编程框架,很大程度的融合了其他响应式编程框架的优点。除了这种自动发送信号的publisher,还有一种可以主动发送信号的Subject,看一下下面的例子。
final class UserData: ObservableObject { let objectWillChange = PassthroughSubject() var allCurrencies: [Currency] { didSet { objectWillChange.send(self) } } } struct ConverterView : View { @EnvironmentObject var userData: UserData var body: some View { return list(userData.allCurrencies) { Item() } } }
UserData作为信号发送方,没有采用publisher的方式,而是利用重写set
方法对其进行了主动发送。
当然如何选择要具体问题,具体分析,苹果提供了相对丰富的方法,应对不同的使用场景。
Operators的使用
当然不只是监听信号这么简单,苹果还为开发者提供了多种Operators,意在更加轻松的让开发者完成函数式编程。代码如下:
static func request(_ kind: XXKind, _ queryItems: [URLQueryItem]?) -> AnyPublisher{ guard var components = URLComponents(url: baseUrl.appendingPathComponent(kind.rawValue), resolvingAgainstBaseURL: true) else { fatalError("Couldn't create URLComponents") } components.queryItems = queryItems let request = URLRequest(url: components.url!) return apiClient.run(request) .map(.value) // 为XXResource中定义的实际值 .eraseToAnyPublisher() }
上述例子中,将返回的数据,通过map()
函数进行了过滤操作,提取出返回值中value的数据,并将其发送给subscribers。如图所示:
总结
本文作为SwiftUI学习的第一章,着重的介绍了combine及其使用方法。文章主要以实战为主,少了许多花里胡哨的介绍跟修饰,希望可以让同学们可以更加快速容易的理解。如开头所说,后续还会总结一下swiftUI中控件在使用时,与正常UIKit不太一样的坑。毕竟国内对于swiftUI的学习并不多,所以希望可以跟同学们一同进步。
以上就是SwiftUI开发总结combine原理简单示例详解的详细内容,更多关于SwiftUI开发combine原理的资料请关注脚本之家其它相关文章!