【Swift/Combine】flatMapメソッドの使い方!publisherを直列処理
この記事からわかること
- SwiftのCombineフレームワーク
- flatMapメソッドの使い方
- 非同期処理を直列に実行する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
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
メソッドもありますが、挙動的には全くの別物ですね。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。