Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Combine】Operatorsを使ってPublishersが出力する値を変換する

 

はじめに

Apple公式ドキュメント(https://developer.apple.com/documentation/combine/receiving-and-handling-events-with-combine)の"Change the Output Type with Operators"にあたる内容です。Operatorsを使ってPublishersが出力する値を変換する処理を試しました。

 

開発環境

 

本題

以下のサンプルでは、Operatorである"compactMap(_:)"で数字の文字列をInt型にキャストしている。出力される値はInt型の1。

compactMapなので、仮にInt型にキャストできない文字列が送信されてきた場合(nilになる場合)は出力されない。

ContentView.swift

import SwiftUI
import Combine

struct ContentView: View {
    private let sampleNotification = Notification.Name("sampleNotification")
    
    private var cancellable: AnyCancellable?
    
    init() {
        cancellable = NotificationCenter.default.publisher(for: sampleNotification, object: nil)
            .compactMap { Int($0.userInfo!["numberString"] as! String) } 
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("finished")
                case .failure(let error):
                    print("error \(error.localizedDescription)")
                }
            },
            receiveValue: { number in
                print(number)
            })
    }
    
    var body: some View {
        Button(action: {
            NotificationCenter.default.post(
                name: sampleNotification,
                object: nil,
                userInfo: ["numberString": "1"]
            )
        }, label: {
            Text("Send notification")
        })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

compactMap以外にも多くのOperatorがある。

※参考:https://developer.apple.com/documentation/combine/just-publisher-operators

例えば、compactMapからmapにサンプルコードを変更して、nilの出力を許容してみる。

変更前

.compactMap { Int($0.userInfo!["numberString"] as! String) }

変更後

.map { Int($0.userInfo!["numberString"] as! String) }

この場合にOperatorの"replaceNil(with:)"を使うと、mapの結果がnilになった場合に代わりの値を出力値として置き換えることができる。

※参考:https://developer.apple.com/documentation/combine/just/replacenil(with:)

サンプルは以下。この場合はnilが出力される代わりにInt型の2が出力される。

ContentView.swift

import SwiftUI
import Combine

struct ContentView: View {
    private let sampleNotification = Notification.Name("sampleNotification")
    
    private var cancellable: AnyCancellable?
    
    init() {
        cancellable = NotificationCenter.default.publisher(for: sampleNotification, object: nil)
            .map { Int($0.userInfo!["numberString"] as! String) }
            .replaceNil(with: 2)
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("finished")
                case .failure(let error):
                    print("error \(error.localizedDescription)")
                }
            },
            receiveValue: { number in
                print(number)
            })
    }
    
    var body: some View {
        Button(action: {
            NotificationCenter.default.post(
                name: sampleNotification,
                object: nil,
                userInfo: ["numberString": "Non-numeric string"]
            )
        }, label: {
            Text("Send notification")
        })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

おわりに

簡単なサンプルですが、Operatorsが上流から流れてきた値を変換してSubscribersに値を流しているイメージを掴むことができました。Operatorsは種類が多く、全部把握するのは骨が折れそうですが実践の中で学んでいければと思います。

 

参考