海韵琴行陈进琪 發表於 2023-2-3 14:48:50

SwiftUI开发总结combine原理简单示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>引言</li><li>SwiftUI是什么?</li><li>如何理解combine</li><ul class="second_class_ul"><li>@propertyWrapper</li><li>Publishers 与 subscribers</li><li>Subject的使用</li><li>Operators的使用</li></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>引言</h2>
<p>最近在自研一个新的项目,在考虑使用的技术栈时,调研了许多,比如react-native,flutter,以及端原生的oc跟swift,但是最终选择了swiftUI + combine,之所以有如此决定,一方面是希望可以完善自己对于iOS系统开发的技术完整性,另一方面希望了解iOS开发未来的一个技术方向,那么闲言少叙切入正题。什么是swiftUI?</p>
<div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/jfs/t5992/50/1038616804/55219/4e703907/592e8ae1N4e21f573.jpg" data-name="移动开发丛书 Swift 3核心技术与开发实践:Swift从入门到精通" data-owner="京东自营" data-price="59.2" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&amp;p=JF8BAMgJK1olXwUFU1xcAUoRA18IGFoTWgACVG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBVVhaDksXHDZNRwYlPnpVMCoKfC93czdyWS0RXmZ5Mw0NaEcbM2gNHF4dXwMBZF5eDkwXAmoIK2sVXDZQOobrvpOysnPcsdTA1ZEyVW5dD00TBm4PHFIUXAQKZF5VDHtUVypcWBhdbTYyV25tOEsnAF9WdVpGWwVWVwlcZhZCXWtPRBJAMwILXVddAEgnAW4JGVklbQ"></div></div>
<p class="maodian"></p><h2>SwiftUI是什么?</h2>
<p>更准确地解释可以移步到苹果开发者中心,概念性的东西,这里不做过多介绍。通过对其的一段时间开发,个人总结,swiftUI绝不是swift+UI这么简单的概念,从设计上,swiftUI十分趋近于web前端,苹果似乎有意将swift做得更加简化,swiftUI也是将开发者得注意力从之前无穷尽地修改UI转到更加关注其app内部的逻辑处理。</p>
<p>简而言之,如果你的项目需求崇尚极简主义,注重逻辑而不采用复杂且臃肿的交互设计,那么swiftUI绝对是值得一试的技术手段。</p>
<p>对于swiftUI的各个组件,官方都给出的事例,这里先不做研究,之后我会在自研项目上线之后,对于其中所用到的组件,遇到的问题,进行逐步汇总,其中会有一些在国内论坛并不容易找到的问题答案。但是现在我们先从基础数据入手,我们先了解一下什么是<strong>combine</strong>。</p>
<p class="maodian"></p><h2>如何理解combine</h2>
<p>谈到<code>combine</code>不得不提的就是swift中的属性修饰器-- @propertyWrapper:</p>
<p class="maodian"></p><h3>@propertyWrapper</h3>
<p>实话实说,如果你还没有用过<strong>propertyWrapper</strong>,那一定要尝试的使用一下,因为这个功能确实太好用了,这里引用官方解释的一段话:</p>
<blockquote><p>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.</p></blockquote>
<p>塑料翻译:</p>
<p>例如,如果你要为数据存储的一些基础属性提供线程安全或者存储它们,你不得不在每一个属性中都写同样的方法,这会让代码变得十分恶心。但是当你使用<strong>propertyWrapper</strong>时,当你为操作代码定义了一个修饰器,那么这些操作代码会应用在它修饰的多个属性中。</p>
<p>上面的解释,是我在学习<strong>propertyWrapper</strong>所能看到的最为通俗的解释。下面也是提供了一段官方代码,帮助理解。</p>
<div class="jb51code"><pre class="brush:cpp;">@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"
</pre></div>
<p>简单解释一下上面的代码,声明一个属性修饰器<strong>TwelveOrLess</strong>,内部的逻辑是输出的属性都比12小,如果大于12则输出12。</p>
<p>下面的<strong>SmallRectangle</strong>包装了两个属性<strong>height</strong>与<strong>width</strong>,当我们为这两个属性赋值,再调用get方法时,可以看到,我们的逻辑代码生效了,输出数字被控制在小于或等于12的值。</p>
<p>无需多余代码,属性修饰器给了swift开发者更多的想象空间。</p>
<p>简单的介绍了一下<strong>propertyWrapper</strong>,接下来我们回归正题,继续说回<strong>combine</strong>。</p>
<p class="maodian"></p><h3>Publishers 与 subscribers</h3>
<p>如果想使用<strong>combine</strong>就不得不了解两个概念,Publishers 与 subscribers。如果你之前有做过Rxswift,或者对于RAC有一定了解的话,对于这两个概念一定不陌生。即便是对于上述框架并不了解,想要理解<strong>Publishers</strong> 与 <strong>subscribers</strong>也不难,因为可以把它理解为观察者模式中的发送者与监听者。</p>
<p>由于官方的事例采用的是通知中心的demo,这在我初学<strong>combine</strong>时给我带来了极大的困扰,因此,本文的事例并不打算采用官方事例,避免给读者带来同样的困扰。而是通过一段自己的部分开源代码对其进行讲解。</p>
<div class="jb51code"><pre class="brush:cpp;">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)
    }
}
</pre></div>
<p>这个例子相对简单,便于入门,我们来看一下,首先,在<strong>XXResourceViewModel</strong>中声明一个被 <strong>@Published</strong>修饰的属性<strong>myAsset</strong>,因为我们刚刚已经介绍过属性修饰器了,所以应该不难理解这个修饰的作用。下面引用官方的一段话。</p>
<blockquote><p>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&rsquo;s value changes.</p></blockquote>
<p>将 @Published 注释添加到类中的属性。这样做使该属性成为了一个publisher,只要该属性的值发生变化,publisher就会发出一个事件。</p>
<p>回到上面一段代码,<strong>publisher</strong>就像是电影《风声》中的老鬼,他的责任就是将自己获取的情报传递给他的上级老枪,那么,谁是<strong>subscribers</strong>老枪。上例中,<strong>Text</strong>控件就是老枪。他与<code>viewModel.myAsset.currency</code>形成了一种绑定关系,一旦<code>viewModel.myAsset.currency</code>发生改变,<strong>Text</strong>接收到信号之后,就会做出对应行动。</p>
<p>看到这有没有人在想到了一种设计模式?没错,就是MVVM。</p>
<p class="maodian"></p><h3>Subject的使用</h3>
<p><strong>combine</strong>作为苹果官方推出的响应式编程框架,很大程度的融合了其他响应式编程框架的优点。除了这种自动发送信号的<strong>publisher</strong>,还有一种可以主动发送信号的<strong>Subject</strong>,看一下下面的例子。</p>
<div class="jb51code"><pre class="brush:cpp;">final class UserData: ObservableObject {
    let objectWillChange = PassthroughSubject&lt;UserData, Never&gt;()
var allCurrencies: {
      didSet {
            objectWillChange.send(self)
      }
    }
}
struct ConverterView : View {
    @EnvironmentObject var userData: UserData
    var body: some View {
         return list(userData.allCurrencies) {
            Item()
         }
    }
}
</pre></div>
<p><strong>UserData</strong>作为信号发送方,没有采用<strong>publisher</strong>的方式,而是利用重写<code>set</code>方法对其进行了主动发送。</p>
<p>当然如何选择要具体问题,具体分析,苹果提供了相对丰富的方法,应对不同的使用场景。</p>
<p class="maodian"></p><h3>Operators的使用</h3>
<p>当然不只是监听信号这么简单,苹果还为开发者提供了多种<strong>Operators</strong>,意在更加轻松的让开发者完成函数式编程。代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">    static func request(_ kind: XXKind, _ queryItems: ?) -&gt; AnyPublisher&lt;XXResource, Error&gt; {
      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()
    }
</pre></div>
<p>上述例子中,将返回的数据,通过<code>map()</code>函数进行了过滤操作,提取出返回值中<strong>value</strong>的数据,并将其发送给<strong>subscribers</strong>。如图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202302/2023020314474701.jpg" /></p>
<p class="maodian"></p><h2>总结</h2>
<p>本文作为SwiftUI学习的第一章,着重的介绍了<strong>combine</strong>及其使用方法。文章主要以实战为主,少了许多花里胡哨的介绍跟修饰,希望可以让同学们可以更加快速容易的理解。如开头所说,后续还会总结一下swiftUI中控件在使用时,与正常UIKit不太一样的坑。毕竟国内对于swiftUI的学习并不多,所以希望可以跟同学们一同进步。</p>
<p>以上就是SwiftUI开发总结combine原理简单示例详解的详细内容,更多关于SwiftUI开发combine原理的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Swift 中的 RegexBuilder学习指南</li><li>Swift中的高阶函数功能作用示例详解</li><li>Swift中的可选项Optional解包方式实现原理</li><li>Swift Package 技巧及混编兼容问题详解</li><li>Swift重构自定义空等运算符&nbsp;“??=”&nbsp;实例</li><li>Swift 重构重载运算符示例解析</li><li>仓库模式及其在Swift 项目中的应用详解</li><li>swift依赖注入和依赖注入容器详解</li><li>Swift 中 Opaque Types学习指南</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: SwiftUI开发总结combine原理简单示例详解