Swift開発手法をポケモンで理解してみよう⑧

ポケモンニワカなのでポケモンに関する情報は正確ではない可能性があります、ご容赦ください。

Swift開発手法をポケモンに例えて覚えてみようとする試み。

Part8、今回はCombine系です。

Part8

AnyCancellable

購読解除のエキスパート、バクフーン

解説
  • タイプ: ほのお (不要な購読を燃やし尽くす)
  • 特性: もうか (購読解除を忘れると危険な状態になる)
  • 得意技:
    • かえんほうしゃ: 不要な購読を強力に解除
    • フレアドライブ: 購読解除を確実に行う
    • にほんばれ: 購読解除のタイミングを制御
  • 性格: まじめ (几帳面に購読を管理)

バクフーンのように、AnyCancellableは:

  • Combineのパブリッシャーの購読を管理し、必要に応じて解除するための機能です。
  • 購読を解除することで、メモリリークや不要な処理を防ぎます。
  • 購読を適切に管理することで、Combineを安全かつ効率的に利用できます。
var subscriptions = Set<AnyCancellable>()

let pokemonPublisher = ["ピカチュウ", "イーブイ", "フシギダネ"].publisher

pokemonPublisher
    .sink(receiveValue: { pokemon in
        print("ゲットだぜ!\(pokemon)")
    })
    .store(in: &subscriptions) // 購読をsubscriptionsに登録
  1. Set<AnyCancellable>() で購読を管理するためのSetを作成します。
  2. pokemonPublisher.sink でパブリッシャーを購読し、値を受け取るたびに “ゲットだぜ![ポケモン名]” と出力します。
  3. .store(in: &subscriptions) で購読を subscriptions に登録します。
  4. これにより、 subscriptions がスコープ外になったときに自動的に購読が解除されます。

receive(on:options:)

空間転送のミュウツー

解説
  • タイプ: エスパー (他のスレッドやキューに情報を転送する能力を表現)
  • 特性: プレッシャー (他の処理に影響を与えず、自分のペースで転送)
  • 得意技:
    • テレポート: 値を別のスレッドやキューに瞬時に転送
    • サイコキネシス: 転送先のキューを操作し、処理の順序を制御
    • バリアー: 転送中の値を保護し、安全性を確保
  • 性格: れいせい (冷静に状況を判断し、最適な転送先を選択)

ミュウツーのように、receive(on:options:)は:

  • パブリッシャーから受け取った値を、指定したスレッドまたはキューで処理できるようにします。
  • UIの更新はメインスレッドで行う必要があるため、UI関連の処理をメインスレッドに転送する際に使用します。
  • バックグラウンドスレッドで実行された処理結果をメインスレッドに転送してUIを更新する際にも利用されます。
let backgroundQueue = DispatchQueue(label: "background")

["ピカチュウ", "イーブイ", "フシギダネ"].publisher
    .receive(on: backgroundQueue) // バックグラウンドキューで処理
    .map { pokemon in
        // 重たい処理 (例: 画像のダウンロード)
        return pokemon.uppercased()
    }
    .receive(on: DispatchQueue.main) // メインスレッドで処理
    .sink(receiveValue: { pokemon in
        print(pokemon) // 結果をUIに反映
    })
    .store(in: &subscriptions)
  1. backgroundQueue というバックグラウンドキューを作成します。
  2. receive(on: backgroundQueue) で、パブリッシャーからの値をバックグラウンドキューで処理するようにします。
  3. map 演算子で、受け取ったポケモン名を大文字に変換する処理をバックグラウンドキューで実行します。
  4. receive(on: DispatchQueue.main) で、処理結果をメインスレッドに転送します。
  5. sink でメインスレッドで受け取った値を処理し、UIに結果を反映します。

sink(receiveCompletion:receiveValue:)

イベントキャッチの名人、ヌオー

解説
  • タイプ: みず/じめん (様々なイベントを受け止め、大地に根を張る安定感を表現)
  • 特性: ちょすい/てんねん (完了と値のイベントを確実に受け取る)
  • 得意技:
    • あまごい: パブリッシャーからのイベントを呼び込む
    • じしん: 受け取った値をしっかりと処理
    • カウンター: エラーが発生した場合の対応
  • 性格: のんき (焦らずイベントを待ち、確実に処理)

ヌオーのように、sink(receiveCompletion:receiveValue:)は:

  • パブリッシャーからのイベントを購読し、完了と値のイベントを受け取るために使用されます。
  • 完了イベント (finished) または失敗イベント (failure(Error)) が発生すると、購読は自動的にキャンセルされます。
  • Combineで最も基本的な購読方法であり、様々な場面で使用されます。
let pokemonPublisher = ["フシギダネ", "ヒトカゲ", "ゼニガメ"].publisher

pokemonPublisher
    .sink(
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("ポケモンゲット完了!")
            case .failure(let error):
                print("エラーが発生しました: \(error)")
            }
        },
        receiveValue: { pokemon in
            print("ゲットだぜ!\(pokemon)")
        }
    )
    .store(in: &subscriptions)
  1. pokemonPublisher というパブリッシャーを作成します。
  2. sink(receiveCompletion:receiveValue:) でパブリッシャーを購読します。
  3. receiveCompletion クロージャーで完了イベント (finished) または失敗イベント (failure(Error)) を受け取ります。
  4. receiveValue クロージャーでパブリッシャーから送信された値を受け取ります。
  5. .store(in: &subscriptions) で購読を subscriptions に登録します。

assign(to:on:)

プロパティ変更の使い手、エーフィ

解説
  • タイプ: エスパー (オブジェクトのプロパティを直接変更する能力を表現)
  • 特性: シンクロ (パブリッシャーの値とオブジェクトのプロパティを同期)
  • 得意技:
    • サイコキネシス: プロパティの値を直接変更
    • リフレクター: 不正な値の代入を防ぐ
    • スキルスワップ: 他のオブジェクトと連携してプロパティを変更
  • 性格: おだやか (冷静に値を受け取り、プロパティに代入)

エーフィのように、assign(to:on:)は:

  • パブリッシャーから受け取った値を、オブジェクトのKeyPathで指定されたプロパティに直接代入します。
  • UIの更新や状態管理など、プロパティの値を動的に変更したい場合に便利です。
  • KVO (Key-Value Observing) のような仕組みをCombineで実現できます。
class PokemonViewModel {
    @Published var name: String = ""
}

let viewModel = PokemonViewModel()
let pokemonPublisher = ["ピカチュウ", "イーブイ", "フシギダネ"].publisher

pokemonPublisher
    .assign(to: \.name, on: viewModel) // viewModel.nameに値を代入
    .store(in: &subscriptions)
  1. PokemonViewModel クラスを作成し、@Published プロパティ name を定義します。
  2. viewModel というインスタンスを作成します。
  3. pokemonPublisher というパブリッシャーを作成します。
  4. assign(to: \.name, on: viewModel) で、パブリッシャーから受け取った値を viewModel.name に代入するように設定します。
  5. .store(in: &subscriptions) で購読を subscriptions に登録します。

@Published

変化を知らせるムチュール

解説
  • タイプ: エスパー (心を読む能力のように、プロパティの変化を検知し、外部に通知する)
  • 特性: ちょすい/よちむ (変更を確実に検知し、準備を整える)
  • 得意技:
    • みらいよち: プロパティの変更を予測し、購読者に通知
    • バトンタッチ: 変更された値を他のオブジェクトに安全に渡す
    • あまえる: Viewに自身の変更を可愛らしくアピール
  • 性格: おだやか (慌てず騒がず、変更を確実に通知)

ムチュールのように、@Publishedは:

  • プロパティの変化を監視し、変更があった際に自動的にパブリッシャーを生成します。
  • このパブリッシャーを通じて、他のオブジェクト(主にView)はプロパティの変化を購読し、それに応じて自身の状態を更新できます。
  • SwiftUIにおけるリアクティブプログラミングの中核を担い、データの変化に応じてUIを動的に更新する仕組みを提供します。
class Pokemon: ObservableObject {
    @Published var name: String = "ピカチュウ"
    @Published var level: Int = 10
}

struct ContentView: View {
    @ObservedObject var pokemon = Pokemon()

    var body: some View {
        VStack {
            Text("名前: \(pokemon.name)")
            Text("レベル: \(pokemon.level)")
            Button("進化!") {
                pokemon.name = "ライチュウ"
                pokemon.level += 10
            }
        }
    }
}
  1. Pokemon クラスは ObservableObject プロトコルに準拠し、@Published でマークされたプロパティ namelevel を持ちます。
  2. ContentView@ObservedObjectPokemon のインスタンスを保持します。
  3. body 内で pokemon.namepokemon.level を表示し、ボタンがタップされるとこれらの値が更新されます。
  4. @Published のおかげで、プロパティの変更が自動的に検知され、Viewが再描画されて最新の値が表示されます。