非同期コードの結合への移行


ジャンプする前にステージを設定してください.
我々は動物の名前と2つのボタンを表示するセルのリストを示しています:動物の絵文字を表示し、他の動物の音を作るには、アプリケーションを持っているとしましょう.

Designer was paid in equity 😛


任意の実世界のアプリと同じように、我々は、チーム上の誰もが自分の方法は最高だと思うように異なる実装されている同様の機能を持っている🤡
/// AnimalsViewController.swift

class AnimalsViewController: UITableViewController {
    ...

    func getAnimals() {
        NetworkingService.getAnimals { [weak self] (result) in
            switch result {
            case .success(let animals):
                self?.animals = animals
                self?.tableView.reloadData()

            case .failure(let error):
                print(error)
            }
        }
    }

    ...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...

        animalCell?.delegate = self
        animalCell?.shouldMakeNoiseForAnimal = { [weak self] animal in
            self?.makeNoise(for: animal)
        }

        ...
    }

    ...
}

extension AnimalsViewController: AnimalCellDelegate {
    func shouldShowEmoji(for animal: Animal) {
        showEmoji(for: animal)
    }
}
このTableViewController を返すAnimal 'サーバーからのSとユーザーが“絵文字”ボタンをクリックしたり、“make noise”ボタンをタップするときのロジックを実装.
/// NetworkService.swift

enum NetworkingService {
    static func getAnimals(completion: @escaping (Result<[Animal], Error>) -> Void) {
        let animals: [Animal] = [.dog, .cat, .frog, .panda, .lion]
        completion(.success(animals))
    }
}
ネットワークサービスを呼び出すときは、サーバーが戻ったときのコールバックとして機能するクロージャを渡しますAnimal データ.
/// AnimalCell.swift

protocol AnimalCellDelegate: AnyObject {
    func shouldShowEmoji(for animal: Animal)
}

class AnimalCell: UITableViewCell {

    ...

    weak var delegate: AnimalCellDelegate?
    var shouldMakeNoiseForAnimal: ((Animal) -> Void)?

    @IBAction func didTapShowEmojiButton() {
        delegate?.shouldShowEmoji(for: animal)
    }

    @IBAction func didTapMakeNoiseButton() {
        shouldMakeNoiseForAnimal?(animal)
    }

    ...
}
The AnimalCell 私たちには、同じことをする2つの異なる方法がありますパスAnimal ボタンがタップされたときにオブジェクト.最初の方法はdidTapShowEmojiButton デリゲートパターンを使用してAnimal 我々の目的AnimalsViewController . もう一つはdidTapMakeNoiseButton これは、動物を送るためにコールバックを使用していますAnimalsViewController .
我々は見ることができるように、我々はこのアプリで非同期コードのいくつかの例があります.コミュニケーションのこれらのタイプの間で理解する1つの重要な違いは、我々が行っている「ネットワーク要求」がただ一つの結果を与えるだけであるということです.いずれかの成功に終わるだろうし、我々の配列を取得しますAnimal 'または、エラーを取得します.
サーバがダウンしてエラーを送信した場合、1秒後にはAnimal 's、それ以外の要求がなされるまで、習慣はそうしません.これらの状況では、単一の結果を返すことを期待しているだけで、我々はFuture .

未来

Future の結果を返すように設計されているPromise . エーPromise は、Swift.Result , いくつかのコントロールフローを書いてから、Promise 好きにするpromise(.success(myObject)) . Future を初期化するPromise 閉鎖とそれから他のように扱われますPublisher ここで、あなたはsink 値を取得するには
我々の代わりにこれを見てみましょうgetAnimals メソッドNetworkingService.swift :
... // enum NetworkingService {

static func getAnimals() -> Future<[Animal], Error> {
    return Future { promise in
        let animals: [Animal] = [.dog, .cat, .frog, .panda, .lion]
        promise(.success(animals))
    }
}

... // NetworkingService closing }
私たちの前の関数シグネチャとコールバックとの間にはたくさんの類似点がありますFuture . 我々は、我々Future を取る閉鎖によってPromise , そして、我々はどちらかを通過success またはfailure 私たちの約束に戻ると、正しい結果を返すことを決定したこの場合、success(animals) .
我々の背中AnimalsViewController 関数シグネチャのエラーが発生しましたNetworkingService.getAnimals 変更.これらのエラーを修正するには、単にFuture 我々が他のものを扱うようにPublisher .
...

var getAnimalsToken: AnyCancellable?
func getAnimals() {
    getAnimalsToken = NetworkingService.getAnimals()
        .sink(
            receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("Subscription finished")

                case .failure(let error):
                    print("Error getting animals -", error)
                }
            },
            receiveValue: { [weak self] animals in
                self?.animals = animals
                self?.tableView.reloadData()
            }
        )
}

...
だから今私たちはsink 我々のためにFuture Publisher これは2つのクロージャにかかります.receiveCompletion and receiveValue . 使用するreceiveCompletion サブスクリプションが終了したら、追加のロジックを行うだけでなく、発生した可能性のあるエラーを処理します.The receiveValue ブロックは、単に期待値で渡すことによってより簡単です例ではAnimal オブジェクト.
心に留めておく必要があるもう一つのことはsink '我々が彼らに言及を保つならば、sは記憶から落ちます.そこは我々の小さな仲間getAnimalsToken 入る.

通過


“ネットワーク要求”と我々のセルからのボタンタップの違いは、タップ/無限の数が含まれていることができます/値は、我々のボタンからストリームを渡されます.我々は複数の結果を操作しているとき/かどうかは、彼らが来るのを停止するかどうかわからない、我々は使用する必要がありますPassthroughSubject .
Unlike Future , the PassthroughSubject サブスクリプションは、最初の結果が送信された後に終了しませんが、代わりに、その必要がありますCompletion<Failure> 手動でストリームを送信します.
では、我々の委任とコールバックコードを取り替えることによって、これを行動で見ましょうAnimalCell.swift with PassthroughSubject .
...

var showEmojiPublisher = PassthroughSubject<Animal, Never>()
var makeNoisePublisher = PassthroughSubject<Animal, Never>()

@IBAction func didTapShowEmojiButton() {
    showEmojiPublisher.send(animal)
}

@IBAction func didTapMakeNoiseButton() {
    makeNoisePublisher.send(animal)
}

...
それはかなり簡単なスワップです.私たちはdelegate and shouldMakeNoiseForAnimal プロパティPassthroughSubject 'sを通して購読できるssink 'コールサイトのS.その後、我々は単に送信Animal それぞれを通してPublisher いずれかのボタンをタップするたびに.
私たちのコミュニケーションの2つの元のフォームを2つに置き換えながらPassthroughSubject 'sは働くでしょう、我々が両方のために参照に掛かる必要があるので、それは理想より少ないですsink 'すべての細胞のs.彼らが同じタイプのものであることは、ちょっと臭いcuzでもありますPublisher 別の名前で.
入れ子を作りましょうAction 我々が送ることができるenumPublisher まだそれぞれのときに何が起こるかについてのコンテキストを維持しながらAction ダウンストリーム.
...

enum Action {
    case showEmoji(Animal)
    case makeNoise(Animal)
}

var actionPublisher = PassthroughSubject<Action, Never>()

...

@IBAction func didTapShowEmojiButton() {
    actionPublisher.send(.showEmoji(animal))
}

@IBAction func didTapMakeNoiseButton() {
    actionPublisher.send(.makeNoise(animal))
}

...
我々の新しいactionPublisher 発行するAnimalCell.Action 'sと現在、我々は1を持っているだけですsink テーブルビューの各セルにハングアップする.
更新時刻cellForRowAt indexPath インAnimalsViewController.swift :
...

// 1
var cellTokens = [IndexPath: AnyCancellable]()

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...

    // 2
    cellTokens[indexPath] = animalCell?.actionPublisher
        .sink { [weak self] action in
            // 3
            switch action {
            case .showEmoji(let animal):
                self?.showEmoji(for: animal)

            case .makeNoise(let animal):
                self?.makeNoise(for: animal)
            }
        }

    ...
}

...
  • cellTokens 私たちはそれぞれに参照を維持することができますsink それで、それは生きていて、価値を観察することができます.
  • 結果を設定したsink 関連するIndexPath .
  • スイッチングaction 両方を扱うことができますAnimalCell.Action 'sは、我々の更新プログラムを更新するにはsink 's論理が我々を加えるならばAction 将来的に.
  • 私たちのアプリは、今までのように動作し続ける必要があります今、私たちの方法は最高です.
    10 xエンジニアの地位へようこそ🙌🏽

    結論


    コンバインはかなり滑らかであり、私たちの機会を単一のパターンにすべてのコミュニケーションパターンを統合するために提供しています.また、誰もが自分の母親は、反応性のフレームワークを使用しているので、同様にネイティブのIOSの開発のための標準になる可能性がありますので、最初のパーティのバージョンで快適になるかもしれません.
    今すぐそこに行く、あなたの上司を説得するすべてのコードをリファクタリングする必要がありますので、情熱的に行う😘