SwiftUI開發總結(一) 這大概是最容易理解的combine
最近在自研一個新的項目,在考慮使用的技術棧時,調研了許多,比如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
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<XXResource, Error> {
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的學習並不多,所以希望可以跟同學們一同進步。
respect!!!