【Swift/Combine】flatMapメソッドの使い方!publisherを直列処理

この記事からわかること

  • SwiftCombineフレームワーク
  • flatMapメソッド使い方
  • 非同期処理直列に実行する方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Combine Framework

環境

flatMapメソッドとは?

公式リファレンス:flatMapメソッド

func flatMap<T, P>(
    maxPublishers: Subscribers.Demand = .unlimited,
    _ transform: @escaping (Self.Output) -> P
) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure

CombineのflatMapメソッドは上流から流れてきたpublisherを整形して新しいpublisherを流すメソッドです。これによりpublisherを直列に繋げたり、値を元に新規でpublisherを作成したりすることができるようになります。

引数maxPublishersには並列に処理をするpublisherの数を指定できます。初期値は無制限unlimitedです。

引数transform部分で上流のpublisher(非同期処理)の出力値(Self.Output)を参照し、下流へPublisherを流すことができます。

使い方

flatMapの使い方を見てみます。Justで生成したパイプラインに直列で非同期処理を挟んでいきます。flatMap内では上流の結果を参照でき、そこから新しいpublisherを生成して下流に流しています。

// Publisherを返す何かしらの非同期処理
func printAddNumber(num: Int) -> AnyPublisher<Int, Error> {
    return Future { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            let reuslt = num + 1
            print("Number: \(reuslt)")
            promise(.success(reuslt))
        }
    }.eraseToAnyPublisher()
}

let cancellable = Just(1)
    .flatMap { number in
        print(number) // 1
        // Justのパイプラインにpublisher(非同期処理)を追加
        return printAddNumber(num: number)
    }
    // ここでは返却する型を明示的に指定しないと以下エラーが発生する
    // Generic parameter 'P' could not be inferred
    .flatMap { number -> AnyPublisher<Int, Error> in
        print(number) // 2
        // さらに別のpublisher(非同期処理)を追加
        return printAddNumber(num: number)
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("All numbers printed successfully")
        case .failure(let error):
            print("Failed with error: \(error)")
        }
    }, receiveValue: { number in
        print("Received number: \(number)")
    })

引数maxPublishersで並列処理数を制限

大元のpublisherから連続で値が流れてくるような場合並列でパイプラインが処理されていくので、出力(処理が完了)される数値の順番は1〜5とか限らず処理の早いものから完了していきます。

このような場合に直列に処理をしていきたい場合maxPublishers.max(並列処理数)を渡すことで制御することが可能です。例えば以下のように1を指定すれば1〜5までが順番に出力されていくようにすることができます。

func printAddNumber(num: Int) -> AnyPublisher<Int, Error> {
    return Future { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            let reuslt = num + 1
            print("Number: \(reuslt)")
            promise(.success(reuslt))
        }
    }.eraseToAnyPublisher()
}

let nums = Array(1...5)
let cancellable = Publishers.Sequence(sequence: nums)
    .flatMap(maxPublishers: .max(1)) { number in
        return printAddNumber(num: number)
    }
    .flatMap { number -> AnyPublisher<Int, Error> i
        return printAddNumber(num: number)
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("All numbers printed successfully")
        case .failure(let error):
            print("Failed with error: \(error)")
        }
    }, receiveValue: { number in
        print("Received number: \(number)")
    })

出力結果

Number: 2
Number: 3
Number: 3
Received number: 3
Number: 4
Number: 4
Received number: 4
Number: 5
Number: 5
Received number: 5
Number: 6
Number: 6
Received number: 6
Number: 7
Received number: 7
All numbers printed successfully

Swiftには多次元配列を一次元配列にするためflatMapメソッドもありますが、挙動的には全くの別物ですね。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index