iOS Combine - 1.初見(jiàn) Combine

什么是Combine

“一套統(tǒng)一的聲明性API,用于處理隨時(shí)間變化的值,其有著支持泛型空执,類型安全,組成優(yōu)先穗椅,請(qǐng)求驅(qū)動(dòng)的特點(diǎn)”

這是 WWDC19 上蘋果推出 Combine 時(shí)的官方描述辨绊。在 iOS 的開(kāi)發(fā)者社區(qū)中基本都將其與 響應(yīng)式編程 掛鉤。如 OC 下的 ReactiveCocoa 與 Swift 下的 Rx 套件(RxSwift房待、RxCocoa等)邢羔,這些都是響應(yīng)式編程框架。

其他第三方響應(yīng)式編程框架不香嗎桑孩?開(kāi)發(fā)中引入第三方框架拜鹤,也就等于引入了一定風(fēng)險(xiǎn)(Bug、性能缺陷流椒、停止維護(hù)敏簿、甚至巨大的代碼量) 。Combine 的優(yōu)勢(shì)就是 “官方出品” ,意味著它能進(jìn)行系統(tǒng)底層優(yōu)化惯裕,更不用說(shuō)其與 Swift温数、UIKit、SwiftUI 等官方框架的深度融合蜻势。所以了解 Combine 是非常必要的撑刺。


Combine 的組成

Combine 的結(jié)構(gòu)跟其他響應(yīng)式框架類似,其中最基礎(chǔ)的組成分為三個(gè)部分握玛,簡(jiǎn)單說(shuō)明如下:

Publisher(發(fā)布者)

值類型够傍,描述了 錯(cuò)誤 是如何產(chǎn)生的,遵循 Publisher 協(xié)議挠铲,協(xié)議中聲明了值類型與錯(cuò)誤類型(OutputFailure)冕屯,聲明 Publisher 時(shí)需要指定這兩者。

// Publisher 協(xié)議主體
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Publisher {

    associatedtype Output
    associatedtype Failure : Error

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

Subscriber(訂閱者)

引用類型拂苹,遵循 Subscriber 協(xié)議安聘,根據(jù)其訂閱的 Publisher 配置有多種接收方法。

// Subscriber 協(xié)議主體
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Subscriber : CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure : Error

    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

訂閱者訂閱發(fā)布者后會(huì)返回一個(gè)遵循 Cancellable協(xié)議的 AnyCancellable瓢棒,作用上類似于其他響應(yīng)式框架中的 dispose浴韭。其控制著訂閱者的釋放,在開(kāi)發(fā)中音羞,可將其作為屬性持有囱桨,當(dāng)頁(yè)面銷毀時(shí),系統(tǒng)釋放 AnyCancellable 時(shí)嗅绰,其會(huì)自動(dòng)調(diào)用其內(nèi)部的 cancel() 方法進(jìn)行資源釋放舍肠。

Operator(操作符)

值類型。其本質(zhì)上也是 Publisher窘面,因此可被 Subscriber 訂閱翠语,其自身也能訂閱其他的 Publisher。Combine 中有不少操作符财边,常見(jiàn)于對(duì)發(fā)布者的數(shù)據(jù)進(jìn)行過(guò)濾修改等操作時(shí)使用肌括。將其看做是個(gè)“中間人”,使用多個(gè) Operator 都是可以的酣难。

【可以通過(guò)一個(gè)例子來(lái)理解三者的關(guān)系:】

關(guān)系舉例

上面這個(gè)圖的例子[發(fā)布者]說(shuō)自己對(duì)錢沒(méi)有興趣谍夭,[操作符]覺(jué)得他說(shuō)謊所以就將數(shù)據(jù)過(guò)濾掉并沒(méi)有繼續(xù)傳遞下去,而[訂閱者]并不會(huì)知道發(fā)布者說(shuō)的話憨募。操作符也可以將發(fā)布者的這句話繼續(xù)傳遞下去讓訂閱者知道紧索,但老夫不愿意。[猛男微笑.gif]


一個(gè)雙向綁定的簡(jiǎn)單例子

Tips:示例基于 Xcode12 beta5

一個(gè)最簡(jiǎn)單的登錄界面菜谣,下面我們就實(shí)現(xiàn)一個(gè)開(kāi)發(fā)中最常見(jiàn)的雙向綁定珠漂,初始 ViewModel 如下:

struct LoginModel {
    var account:String = ""
}

class LoginVM {
    
    // 登錄狀態(tài)
    enum LoginState {
        case none            
        case success
        case error
    }
    
    // model
    var model = LoginModel()
    
    // 登錄
    func login(psw:String = "") {...}
}

將賬號(hào)輸入與模型綁定

在 WWDC19 時(shí)晚缩,蘋果整合了combine 與 Notifaction、URLSession媳危、Userdefault 三個(gè)系統(tǒng)組件荞彼。而在寫這個(gè)demo的時(shí),本想自定義個(gè) Publisher待笑,結(jié)果 Textfiled 竟也可以聯(lián)想出 Combine 相關(guān)方法鸣皂。本篇主要是介紹,老夫就偷個(gè)懶用系統(tǒng)的滋觉。

// 獲取發(fā)布者
let publisher = accountTF.publisher(for: \.text, options: NSKeyValueObservingOptions.new)

// 訂閱發(fā)布者
accountCancel = publisher.sink { [weak self](text) in
    if let self = self {
        // 將如數(shù)的賬號(hào)賦值給我們的 model
        self.viewModel.model.account = text ?? ""
    }
}

使用方法跟 RxSwift 等三方響應(yīng)式框架一樣签夭,并且更加的高效好用,僅僅兩句代碼椎侠。

第一句我們通過(guò) 賬號(hào)輸入組件獲取到發(fā)布者 publisher。其中的 \.text 是 Swift5.0(沒(méi)記錯(cuò)的話) 之后加入的特性措拇,相比 OC 中 KVC 使用字符串來(lái)指定關(guān)鍵字更加安全我纪,避免了輸入錯(cuò)誤引發(fā)問(wèn)題。

第二句就是創(chuàng)建訂閱者并讓其訂閱發(fā)布者丐吓,這里使用到了 sink 方法浅悉,其是 Publisher 協(xié)議的擴(kuò)展方法: 將閉包綁定給訂閱者并訂閱發(fā)布者 ,開(kāi)發(fā)者只需要通過(guò) sink 方法提供一個(gè)訂閱者回調(diào)的閉包給發(fā)布者即可實(shí)現(xiàn)訂閱券犁,意味著不用開(kāi)發(fā)者自己去實(shí)現(xiàn)訂閱者术健、再綁定發(fā)布者的操作。

注意 accountCancel粘衬,其為 AnyCancelable 類型荞估,主要實(shí)現(xiàn)了 Cancellable 協(xié)議,協(xié)議里只有一個(gè) cancel 方法稚新。只需要知道它由訂閱者實(shí)現(xiàn)勘伺,在其自身被釋放時(shí)調(diào)用 cancel 來(lái)釋放資源,這么一看跟 RxSwift 中的 DisposeBag 類似褂删。其與訂閱者生命周期相關(guān)飞醉,持有它,訂閱就會(huì)一直生效屯阀。

將請(qǐng)求結(jié)果跟視圖綁定

上邊我們用了系統(tǒng)生成的 publisher缅帘,那關(guān)于 model 層有沒(méi)有啥系統(tǒng)提供的東西呢?還真有难衰。

@Published var loginState:LoginState = .none

@Published 是系統(tǒng)提供給我們用來(lái)修飾屬性的钦无,其只能在 Class 中使用。從其寫法能看出它其實(shí)是一個(gè)屬性包裝器(PropertyWrapper):

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct Published<Value> {

    public init(wrappedValue: Value)
    public init(initialValue: Value)

    // 發(fā)布者定義
    public struct Publisher : Publisher {
        public typealias Output = Value
        public typealias Failure = Never

        public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
    }

    // 發(fā)布者實(shí)例
    public var projectedValue: Published<Value>.Publisher { mutating get set }
}

由上可知召衔,@Published 的屬性包裝器里讓屬性持有了個(gè)自己聲明的發(fā)布者铃诬。這樣就可以讓被@Published標(biāo)記的屬性自動(dòng)生成發(fā)布者。

訂閱也很簡(jiǎn)單:

loginStateCancel = viewModel.$loginState.sink { (state) in
    // 各種操作
}

使用 sink 方法訂閱 viewModel.$loginState,這個(gè) loginState 不是個(gè)枚舉么...關(guān)鍵在 $ 符號(hào)上...這里的viewModel.$loginState 實(shí)際上返回的是:

Published<LoginVM.LoginState>.Publisher

一個(gè)發(fā)布者趣席。通過(guò)$ 符號(hào)訪問(wèn)屬性是獲取屬性包裝器中的自定義屬性 projectedValue 的值兵志,在 @Published 中,這個(gè)自定義屬性就是系統(tǒng)生成的發(fā)布者宣肚。關(guān)于屬性包裝器可以看看Property Wrappers

這里延伸出了一個(gè)問(wèn)題: 每個(gè)需要綁定/觀察的鍵都被 @Published 標(biāo)記想罕,然后又訂閱,可當(dāng)面對(duì)的是一個(gè)復(fù)雜的模型時(shí)就會(huì)產(chǎn)生大量重復(fù)操作霉涨。有沒(méi)有...

當(dāng)然有按价!使用 ObservableObject協(xié)議:

class LoginVM: ObservableObject {...}

ObservableObject 協(xié)議中定義了一個(gè)發(fā)布器,并在協(xié)議的擴(kuò)展中實(shí)現(xiàn)了默認(rèn)的發(fā)布器笙瑟,這樣就讓遵循協(xié)議的類默認(rèn)擁有了一個(gè)發(fā)布器楼镐,獲取回調(diào)發(fā)布器的屬性為objectWillChange

Tips:被 @Published 標(biāo)記的屬性更新前會(huì)回調(diào)往枷,未被標(biāo)記的屬性則不會(huì)框产。

了解了這些,就可以通過(guò) viewModel 的 objectWillChange 獲取到發(fā)布者并訂閱來(lái)監(jiān)聽(tīng)所有被 @Published 標(biāo)記的屬性更改的回調(diào)

loginStateCancel = viewModel.objectWillChange.sink { [weak self]() in
    print("登錄狀態(tài)即將發(fā)生改變:\(self?.viewModel.loginState)")
}
啥也不說(shuō)了

細(xì)心的你肯定發(fā)現(xiàn) objectWillChange 返回的發(fā)布者错洁,會(huì)在操作前回調(diào)秉宿,此時(shí)去獲取屬性還是舊值,查看協(xié)議后發(fā)現(xiàn)目前只有這么一個(gè)發(fā)布器屯碴,未來(lái)會(huì)不會(huì)推出objectDidChange不得而知描睦,我們是等蘋果還是自己動(dòng)手實(shí)現(xiàn)一個(gè)更新后的發(fā)布者,甚至粗暴的加個(gè)異步延時(shí)呢导而?挖了個(gè)坑忱叭。


總結(jié)

總的來(lái)說(shuō),可將 Combine 看作一種觀察者模式嗡载,其分為 [發(fā)布者][訂閱者] 窑多,兩者配合處理隨著時(shí)間變化的值,還有操作符用來(lái)修改發(fā)布者的值洼滚。

Combine 在 WWDC19 上推出埂息,而 WWDC20 上沒(méi)有什么大的變動(dòng),倒是默默的推出了更多融合到系統(tǒng)架構(gòu)中的功能遥巴。說(shuō)明 Combine 的架構(gòu)基本確定千康,未來(lái)也不會(huì)再有什么傷筋動(dòng)骨的變動(dòng),以后只會(huì)新增更多的支持特性铲掐。

如果開(kāi)發(fā)者的應(yīng)用從 iOS13 為最低版本開(kāi)發(fā)新應(yīng)用的話拾弃,推薦使用 Combine 替代 ReactiveCocoa,RxSwift 等三方框架摆霉。

下一篇豪椿,深入一步奔坟,看看在 Combine 中如何解鎖自定義的姿勢(shì)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載搭盾,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者咳秉。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸯隅,隨后出現(xiàn)的幾起案子澜建,更是在濱河造成了極大的恐慌,老刑警劉巖蝌以,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炕舵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跟畅,警方通過(guò)查閱死者的電腦和手機(jī)咽筋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碍彭,“玉大人晤硕,你說(shuō)我怎么就攤上這事”蛹桑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵舰褪,是天一觀的道長(zhǎng)皆疹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)占拍,這世上最難降的妖魔是什么略就? 我笑而不...
    開(kāi)封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮晃酒,結(jié)果婚禮上表牢,老公的妹妹穿的比我還像新娘。我一直安慰自己贝次,他們只是感情好崔兴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蛔翅,像睡著了一般敲茄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上山析,一...
    開(kāi)封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天堰燎,我揣著相機(jī)與錄音,去河邊找鬼笋轨。 笑死秆剪,一個(gè)胖子當(dāng)著我的面吹牛赊淑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仅讽,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼陶缺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了何什?” 一聲冷哼從身側(cè)響起组哩,我...
    開(kāi)封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎处渣,沒(méi)想到半個(gè)月后伶贰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罐栈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年黍衙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荠诬。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琅翻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柑贞,到底是詐尸還是另有隱情方椎,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布钧嘶,位于F島的核電站棠众,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏有决。R本人自食惡果不足惜闸拿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望书幕。 院中可真熱鬧新荤,春花似錦、人聲如沸台汇。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)励七。三九已至智袭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掠抬,已是汗流浹背吼野。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留两波,地道東北人瞳步。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓闷哆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親单起。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抱怔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容