WWDC Quick Look 💓 By SwiftGGTeam
Generalize APIs with parameter packs

Generalize APIs with parameter packs

元の動画を見る

ハイライト

Swift 5.9 ではパラメータ パック (パラメータ パック) が導入されています。each型パラメータパックを宣言し、repeat繰り返しパターンを定義すると、単一のジェネリック関数でパラメーターの数とその型の両方を抽象化できるようになり、N 関数のオーバーロードの定型コードが完全に排除されます。

主な内容

Swift のジェネリックス システムはすでに型を非常にうまく抽象化しています。Array<Element>どのようなタイプでも対応できるので、query<Payload>(_ item: Request<Payload>) -> Payloadあらゆる種類のリクエストを処理できます。

ただし、複数のリクエストを同時に開始する必要がある場合に問題が発生します。各リクエストは異なるタイプを返す可能性があり、その結果をリクエストのタイプに対応する各位置のタイプを持つタプルにする必要があります。

02:04

オーバーロード地獄

現在のアプローチは、複数のオーバーロードを記述することです。

func query<Payload>(_ item: Request<Payload>) -> Payload

func query<Payload1, Payload2>(
    _ item1: Request<Payload1>,
    _ item2: Request<Payload2>
) -> (Payload1, Payload2)

func query<Payload1, Payload2, Payload3>(
    _ item1: Request<Payload1>,
    _ item2: Request<Payload2>,
    _ item3: Request<Payload3>
) -> (Payload1, Payload2, Payload3)

func query<Payload1, Payload2, Payload3, Payload4>(
    _ item1: Request<Payload1>,
    _ item2: Request<Payload2>,
    _ item3: Request<Payload3>,
    _ item4: Request<Payload4>
) -> (Payload1, Payload2, Payload3, Payload4)

4 つのオーバーロード、コードはほぼ同じです。 5 つのパラメーターを渡すと、コンパイラーはエラーを報告します。さらに悪いことに、このオーバーロード パターンは Swift 標準ライブラリとサードパーティ ライブラリの両方に蔓延しています。

03:23

パラメータパッケージの概念

パラメーター パッケージには、次の 2 つの新しい概念が導入されています。

タイプ パック (タイプ パック) は、任意の数のタイプを保持できます。たとえば、型パッケージには次のものを含めることができます。BoolIntString3つのセパレートタイプ。

バリュー パックには、任意の数の値を保持できます。たとえば、値パッケージには以下を含めることができます。true10""3 つの独立した値。

型パッケージと値パッケージは一緒に使用されます。型パッケージ内の各型は、値パッケージ内の同じ位置の値に対応します。位置 0 のtrue種類はBool、位置 1 の10種類はInt、位置 2 の""種類はString

04:41

パラメータパッケージを使用してクエリを書き換えます

パラメーター パックを使用すると、N 個のオーバーロードが 1 つの関数に圧縮されます。

func query<each Payload>(
    _ item: repeat Request<each Payload>
) -> (repeat each Payload)

キーポイント:

  • each Payload型パラメータパックを宣言する -repeat Request<each Payload>パラメータリスト内で繰り返し出現することを示しますRequest<each Payload>
  • repeat each Payload返されたタプル内の繰り返しの出現を表します。each Payload

次のように呼び出すのは完全に自然です。

let result = query(Request<Int>())
// result 类型是 Int

let results = query(Request<Int>(), Request<String>(), Request<Bool>())
// results 类型是 (Int, String, Bool)

コンパイラはコンパイル時にパラメータ パックを展開して、タイプ セーフなコードを生成します。オーバーロードを記述する必要はありません。

09:37

##詳細

パラメータパッケージの構文規則

型パラメータパッケージを宣言するために使用されますeachキーワード:

func query<each Payload>(
    _ item: repeat Request<each Payload>
) -> (repeat each Payload)

repeatコンマ区切りのリスト (タプル型、関数パラメータ リスト、汎用パラメータ リスト) でのみ使用できます。どこでも使用できません。

制約はパラメータ パッケージに追加できます。

// 所有类型都遵循 Equatable
func query<each Payload: Equatable>(
    _ item: repeat Request<each Payload>
) -> (repeat each Payload)

// 或者用 where 子句
func query<each Payload>(
    _ item: repeat Request<each Payload>
) -> (repeat each Payload)
    where repeat each Payload: Equatable

通常の汎用パラメータとパラメータ パックを混合することもできます。

func query<FirstPayload, each Payload>(
    _ first: Request<FirstPayload>,
    _ item: repeat Request<each Payload>
) -> (FirstPayload, repeat each Payload)
    where FirstPayload: Equatable, repeat each Payload: Equatable

11:03

パラメータパッケージの実装

パラメータパック機能の実装repeat各要素を反復処理するには:

struct Request<Payload> {
    func evaluate() -> Payload
}

func query<each Payload>(
    _ item: repeat Request<each Payload>
) -> (repeat each Payload) {
    return (repeat (each item).evaluate())
}

キーポイント:

  • each itemバリューバッグ内の各要素を解凍します。 -(each item).evaluate()各要素でメソッドを呼び出す -repeat (...)結果を価値のあるパッケージに詰め込む
  • 外側の括弧(repeat ...)値パッケージをタプルに組み立てる

13:42

より複雑な例

パラメータ パックは、入力タイプと出力タイプが異なる場合にも機能します。

protocol RequestProtocol {
    associatedtype Input
    associatedtype Output
    func evaluate(_ input: Input) -> Output
}

struct Evaluator<each Request: RequestProtocol> {
    var item: (repeat each Request)

    func query(
        _ input: repeat (each Request).Input
    ) -> (repeat (each Request).Output) {
        return (repeat (each item).evaluate(each input))
    }
}

ここ: -each Requestは型パラメータ パックであり、各型は以下に続きますRequestProtocol

  • (each Request).Input各リクエストタイプの入力関連タイプを取得します。 -(each Request).Output各リクエストタイプの出力関連タイプを取得します。 -each inputそしてeach item入力値パッケージと要求値パッケージを別々に解凍する -(each item).evaluate(each input)対応する入力を対応するリクエストに渡します

16:04

制御フローを含むパラメーター パッケージ

パラメータパックと組み合わせることもできますtry以下と組み合わせて使用​​します。

protocol RequestProtocol {
    associatedtype Input
    associatedtype Output
    func evaluate(_ input: Input) throws -> Output
}

struct Evaluator<each Request: RequestProtocol> {
    var item: (repeat each Request)

    func query(
        _ input: repeat (each Request).Input
    ) -> (repeat (each Request).Output)? {
        do {
            return (repeat try (each item).evaluate(each input))
        } catch {
            return nil
        }
    }
}

repeat try (...)各要素に適用されることを示しますtry。いずれかのリクエストでエラーがスローされた場合、全体doブロックエントリーcatch

17:05

重要なポイント

ネットワーク層 API をリファクタリング

プロジェクトに複数のプロジェクトがある場合、fetchメソッド オーバーロード (1 パラメーター、2 パラメーター、3 パラメーター…) は、パラメーター パックを使用して 1 つに結合されます。発信者のエクスペリエンスは変わらず、メンテナンスコストが大幅に削減されます。

入口:置くfunc fetch<T1, T2>(_ r1: Request<T1>, _ r2: Request<T2>) -> (T1, T2)オーバーロードを次のように置き換えますfunc fetch<each T>(_ request: repeat Request<each T>) -> (repeat each T)

タイプセーフな依存関係注入コンテナー

パラメータパッケージを使用して実装しますContainer.resolve()複数の依存関係を同時に解決する方法:

let (db, network, cache) = container.resolve(Database.self, Network.self, Cache.self)

エントリ: 定義func resolve<each T>() -> (repeat each T)、コンテナ内の型に応じて、対応するインスタンスを見つけます。

複数の同時リクエストの均一なカプセル化

SwiftUI または Combine プロジェクトでは、複数のリクエストが同時に開始され、すべてが完了した後に UI が更新されるシナリオがよくあります。パラメータパッケージをカプセル化するconcurrent関数:

let (user, posts, settings) = await concurrent(
    fetchUser(),
    fetchPosts(),
    fetchSettings()
)

入り口:func concurrent<each T>(_ task: repeat @Sendable () async -> each T) async -> (repeat each T)、内部使用用withTaskGroup同時実行。

タプル型用の汎用ツール

書くzip関数、複数入れるOptional1つにパッケージ化されたOptionalタプル:

let result = zip(opt1, opt2, opt3)
// result: (T1, T2, T3)?

入り口:func zip<each T>(_ value: repeat each T?) -> (repeat each T)?、すべての値が非 nil の場合はタプルを返し、それ以外の場合は nil を返します。

関連セッション

コメント

GitHub Issues · utterances