SwiftUI 與 Combine(一)

創(chuàng)建Publisher 以及對其進行訂閱

新建Playground盏求,添加工具方法用于輸出示例代碼的運行情況

public func example(of description: String, action: () -> Void) {
    print("\n——— Example of:", description, "———")
    action()
}

首先在swift標準庫中我們可以使用如下方式發(fā)送和監(jiān)聽一個通知

/// 原始使用Notification的示例
example(of: "Notification") {
    let myNotification = Notification.Name(rawValue: "MyNotification")
    let observer = NotificationCenter.default.addObserver(forName: myNotification, object: nil, queue: nil) { _ in
        print("receive notification")
    }
    NotificationCenter.default.post(name: myNotification, object: nil, userInfo: nil)
    NotificationCenter.default.removeObserver(observer)
}

/*輸出:
——— Example of: Notification ———
receive notifacation
*/

同樣我們使用Combine后也可以對通知進行[圖片上傳中...(截屏2020-03-03下午2.51.48.png-66e51d-1583218332269-0)]
訂閱

/// 使用Combine管理Notification的例子
import Combine
example(of: "Combine") {
    let myNotification = Notification.Name(rawValue: "MyNotification")
    //創(chuàng)建一個publisher
    let publisher = NotificationCenter.default.publisher(for: myNotification, object: nil)
    //創(chuàng)建一個訂閱
    let subscription = publisher.sink { _ in
        print("receive notification")
    }
    NotificationCenter.default.post(name: myNotification, object: nil, userInfo: nil)
    //結(jié)束訂閱
    subscription.cancel()
}

/*輸出:
——— Example of: Combine ———
receive notifacation
*/
sink

注意以上代碼中的skin方法施敢,與其本意下沉似乎沒有什么關(guān)系,我們在Xcode中查看其源代碼聲明:

extension Publisher where Self.Failure == Never {

    /// 用閉包添加一個subscriber 
    ///
    /// 此方法會創(chuàng)建一個 subscriber 并且立即訂閱 and immediately requests an unlimited number of values, prior to returning the subscriber.
    /// 返回一個可取消的實例仑扑;用于在結(jié)束對接收值的使用時刪除訂閱流。
        public func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
}

以上我們可以利用skin創(chuàng)建一個subscriber了愕掏,同時我們也發(fā)現(xiàn)skin還有另一個定義

Just
example(of: "Just") {
    let just = Just("Hello world!")
    _ = just.sink(
        receiveCompletion: {
            print("Received completion", $0)
        },
        receiveValue: {
            print("Received value", $0)
    })
}

/*輸出
——— Example of: Just ———
Received value Hello world!
Received completion finished  
*/

skin的這種訂閱可以分別訂閱到完成事件以及發(fā)出的值事件

Just和RXSwift中一樣會創(chuàng)建一個只發(fā)出發(fā)出一個元素和一個完成事件的publisher滓侍。

assign(to:on:)

除了skin之外狈醉,內(nèi)置的assign(to:on:)操作符還允許您將接收到的值分配給對象的KVO屬性。

example(of: "assign(to:on:)") {    
    class SomeObject {
    // 使用具有打印新值的didSet屬性觀察者的屬性定義類惠险。
        var value: String = "" {
            didSet { print(value)}
        }
    }
    // 創(chuàng)建該類的實例舔糖。
    let object = SomeObject()
    // 從字符串數(shù)組創(chuàng)建發(fā)布服務(wù)器。
    let publisher = ["Hello", "world!"].publisher
     // publisher將接收到的每個值分配給對象的value屬性莺匠。
    _ = publisher.assign(to: \.value, on: object)
}
/*輸出
——— Example of: assign(to:on:) ———
Hello
world!
*/
Cancellable

當訂閱publisher完成并且不再希望從publisher接收值時,最好取消訂閱以釋放資源并停止任何相應(yīng)的活動十兢,例如網(wǎng)絡(luò)調(diào)用趣竣。
訂閱返回anyCancelable的實例作為“取消令牌”,這樣在完成訂閱后就可以取消訂閱旱物。
anyCancelable符合Cancelable協(xié)議遥缕,因此可以使用cancel()方法取消訂閱。
例如在 example(of: "Combine") 例子中的subscription.cancel()就取消了對消息中心的訂閱和removeObserver(observer)起到同樣的效果宵呛。如果不調(diào)用cancel()那么直到publisher發(fā)出完成事假单匣,訂閱才會取消

注意:也可以忽略訂閱的返回值(例如,_=just.sink…)宝穗。但是有一個警告:如果您不在整個項目中存儲訂閱户秤,則一旦程序流退出其創(chuàng)建的范圍(方法結(jié)束后訂閱對象銷毀,訂閱結(jié)束)逮矛,該訂閱將被取消鸡号!

Publisher

我們在Xcode中點擊跳轉(zhuǎn)到Publisher的定義

public protocol Publisher {
    /// 發(fā)送的值的類型
    associatedtype Output

    /// Publisher可能產(chǎn)生的錯誤類型;
    /// 如果保證Publisher不會產(chǎn)生錯誤须鼎,則使用`Never`鲸伴。
    associatedtype Failure : Error

    /// 在調(diào)用 Publisher的`subscribe(_:)`方法時方法內(nèi)部會調(diào)用此方法去附加`Subscriber`
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

extension Publisher {
    /// 將指定的subscriber附加到此publisher
    /// 調(diào)用此方法附加而不是`receive(subscriber:)`.
    public func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

還有關(guān)聯(lián)的 Output和Failure一致是才能附加subscriber到publisher

Subscriber
public protocol Subscriber : CustomCombineIdentifierConvertible {

    /// 可以接收的值的類型
    associatedtype Input

    /// 可以接收的錯誤類型;如果不接收錯誤晋控,則使用 `Never`
    associatedtype Failure : Error

    /// 調(diào)用此方法以提供訂閱
    func receive(subscription: Subscription)

    /// 發(fā)送剛發(fā)布的新值
    func receive(_ input: Self.Input) -> Subscribers.Demand

    /// 發(fā)送錯誤或完成事件
    func receive(completion: Subscribers.Completion<Self.Failure>)
}
Subscription

連接publisher 和 subscriber 是 subscription. 下面是 Subscription protocol的定義

public protocol Subscription : Cancellable, CustomCombineIdentifierConvertible {
    ///  告訴 publisher 可以發(fā)送多少個值到 subscriber.
    func request(_ demand: Subscribers.Demand)
}

subscriber說明其愿意接收多少值的概念稱為backpressure汞窗。如果沒有它或其他策略,subscriber可能會收到來自publisher的超出其處理能力的大量值赡译,這可能會導(dǎo)致問題仲吏。在后面我們會詳細介紹,你也可以瀏覽王巍 (@onevcat)的這篇文章關(guān)于 Backpressure 和 Combine 中的處理

注意Subscriber中的func receive(_ input: Self.Input) -> Subscribers.Demand方法捶朵,返回值是一個Subscribers.Demand蜘矢,即使訂閱最開始時設(shè)置了接受的最大值,我們在每次收到新的值都可以調(diào)整收到的最大值综看,這個值是累加的品腹,并且傳遞負值時會直接返回 fatalError,所以這個值只會越來越大而不能減少它

自定義subscriber
example(of: "Custom Subscriber") {

    // 1 通過整數(shù)范圍創(chuàng)建一個publisher
    let publisher = (1...6).publisher

    // 2 自定義一個整型的Subscriber實現(xiàn)協(xié)議
    final class IntSubscriber: Subscriber {
        // 3 指定接收值的類型和錯誤類型
        typealias Input = Int
        typealias Failure = Never

        // 4 實現(xiàn)協(xié)議方法 publisher會調(diào)用該方法
        func receive(subscription: Subscription) {
            //簡單實現(xiàn)為接收訂閱的值最多三個
            subscription.request(.max(3))
        }

        // 5 實現(xiàn)接受到值時的方法红碑,返回接收值的最大個數(shù)變化
        func receive(_ input: Int) -> Subscribers.Demand {
            //這里簡單實現(xiàn)為打印值舞吭,返回 .none,意思就是不改變最大接收數(shù)量 .none 等價于 .max(0).
            print("Received value", input)
            return .none
        }

        // 6 實現(xiàn)接收到完成事件的方法
        func receive(completion: Subscribers.Completion<Never>) {
            print("Received completion", completion)
        }
    }
    // 訂閱publisher
    publisher.subscribe(IntSubscriber())
}

/*輸出
——— Example of: Custom Subscriber ———
Received value 1
Received value 2
Received value 3
*/

這里在一開始指定了只接受3個值(.max(3))并且在后續(xù)接受到新值時沒有更改最大值(.none)泡垃,因此直接受到publisher發(fā)出的前三個值。如果我們將一開始的.none改成.unlimited或者接受到新值后我們每次加一最大值(.max(1))那么我們就會收到所有值羡鸥,并且收到完成事件

輸出結(jié)果應(yīng)該會是:
——— Example of: Custom Subscriber ———
Received value 1
Received value 2
Received value 3
Received value 4
Received value 5
Received value 6
Received completion finished

Future

就像使用Just創(chuàng)建一個publisher一樣蔑穴,F(xiàn)uture可以用于異步生成一個值,然后完成惧浴。
我們通過Xcode查看Future的定義

final public class Future<Output, Failure> : Publisher where Failure : Error {
    public typealias Promise = (Result<Output, Failure>) -> Void
    
    public init(_ attemptToFulfill: @escaping (@escaping Future<Output, Failure>.Promise) -> Void)
    final public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}

Future是一個 publisher 會異步產(chǎn)生一個值然后完成或者失敗存和,Promise是閉包的類型別名,它接收一個Reuslt包含輸出的值或者失敗

Result類型為Swift5新增內(nèi)容衷旅,你可以查看Swift5新特性 & XCode 10.2更新獲取更多信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捐腿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子柿顶,更是在濱河造成了極大的恐慌茄袖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘁锯,死亡現(xiàn)場離奇詭異宪祥,居然都是意外死亡,警方通過查閱死者的電腦和手機家乘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門蝗羊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烤低,你說我怎么就攤上這事肘交。” “怎么了扑馁?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵涯呻,是天一觀的道長。 經(jīng)常有香客問我腻要,道長复罐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任雄家,我火速辦了婚禮效诅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘趟济。我一直安慰自己乱投,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布顷编。 她就那樣靜靜地躺著戚炫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪媳纬。 梳的紋絲不亂的頭發(fā)上双肤,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天施掏,我揣著相機與錄音,去河邊找鬼茅糜。 笑死七芭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔑赘。 我是一名探鬼主播狸驳,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缩赛!你這毒婦竟也來了锌历?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤峦筒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后窗慎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體物喷,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年遮斥,在試婚紗的時候發(fā)現(xiàn)自己被綠了峦失。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡术吗,死狀恐怖尉辑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情较屿,我是刑警寧澤隧魄,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站隘蝎,受9級特大地震影響购啄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘱么,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一狮含、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧曼振,春花似錦几迄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至集索,卻和暖如春屿愚,著一層夾襖步出監(jiān)牢的瞬間汇跨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工妆距, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留穷遂,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓娱据,卻偏偏與公主長得像蚪黑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子中剩,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

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

  • 在具體介紹 Combine 之前忌穿,有兩個重要的概念需要簡要介紹一下: 觀察者模式 響應(yīng)式編程 觀察者模式 觀察者模...
    沒八阿哥的程序閱讀 9,005評論 2 21
  • SwiftUI 與 Combine(簡介)什么是SwiftUI?什么是Combine?響應(yīng)式編程:異步編程:何時可...
    DkJone閱讀 7,120評論 0 4
  • 文章鏈接 盡管今年的WWDC已經(jīng)落幕结啼,但在過去的一個多月時間掠剑,蘋果給iOS開發(fā)者帶來了許多驚喜,其中堪稱最重量級的...
    sindri的小巢閱讀 6,924評論 4 14
  • Combine是什么 a declarative Swift API for processing values ...
    zzzworm閱讀 1,798評論 0 1
  • 簡介 Combine是Apple在2019年WWDC上推出的一個新框架郊愧。該框架提供了一個聲明性的Swift API...
    云天涯丶閱讀 24,452評論 5 22