Swift5.7で汎用プロトコルを使用して動的ディスパッチを実現する方法

動的ディスパッチは、オブジェクト指向プログラミング(OOP)の最も重要なメカニズムの1つです。これは、実行時のポリモーフィズムを可能にするコアメカニズムであり、開発者はコンパイル時ではなく実行時に実行パスを決定するコードを記述できます。

OOPで動的ディスパッチを実現するのは簡単なようですが、プロトコル指向プログラミング(POP)の場合はそうではありません。プロトコルを使用して動的ディスパッチを実行しようとすると、Swiftコンパイラのさまざまな制限により、常に予期しない問題が発生します。

Swift 5.7のリリースにより、これらすべてが歴史になりました。POPの領域で動的ディスパッチを実現することはかつてないほど容易になりました。この記事では、Swift 5.7からどのような改善が得られ、関連するタイプのプロトコルを使用して動的ディスパッチを実現するために何が必要かを調べてみましょう。

ですから、これ以上面倒なことはせずに、すぐに始めましょう!

ノート:

someSwiftのandキーワードに慣れていない場合は、最初に「 Swift5.7の「some」および「any」キーワードを理解する」anyというブログ投稿を読むことを強くお勧めします。

 

ファーストシングスファースト

Swift 5.7の改善点を紹介する前に、この記事全体でサンプルコードに必要なプロトコルと構造体を定義しましょう。

struct Gasoline {
    let name = "gasoline"
}

struct Diesel {
    let name = "diesel"
}

protocol Vehicle {

    associatedtype FuelType
    
    var name: String { get }

    func startEngin()
    func fillGasTank(with fuel: FuelType)
}

struct Car: Vehicle {

    let name: String

    func startEngin() {
        print("\(name) enjin started!")
    }

    func fillGasTank(with fuel: Gasoline) {
        print("Fill \(name) with \(fuel.name)")
    }
}

struct Bus: Vehicle {

    let name: String

    func startEngin() {
        print("\(name) enjin started!")
    }

    func fillGasTank(with fuel: Diesel) {
        print("Fill \(name) with \(fuel.name)")
    }

}

上記の定義は、前回の記事で使用したものと似ていますが、少しひねりがあります。このVehicleプロトコルでは、2つの機能要件がstartEngin()ありfillGasTank(with:)ます。デモンストレーションのために、これら2つの関数と構造体の両方を使用して動的ディスパッチを実現しようとしCarますBus

 

Swift5.6以下の汎用プロトコルの制限

ここで、startAllEngin()以下に示すように、異種配列を受け入れる関数を作成するとします。

// Heterogeneous array with `Car` and `Bus` elements
// 🔴 Compile error: Protocol ‘Vehicle’ can only be used as a generic constraint because it has Self or associated type requirements
let vehicles: [Vehicle] = [
    Car(name: "Car_1"),
    Car(name: "Car_2"),
    Bus(name: "Bus_1"),
    Car(name: "Car_3"),
]

func startAllEngin(for vehicles: [Vehicle]) {
    for vehicle in vehicles {
        vehicle.startEngin()
    }
}

// Execution
startAllEngin(for: vehicles)

Swift 5.6では、「プロトコル'Vehicle'は、Selfまたは関連するタイプ要件があるため、ジェネリック制約としてのみ使用できます」というエラーが表示されるため、これは文字通り不可能であることに気付くでしょう。Swiftコンパイラは、タイプ()が関連付けられているVehicleため、要素タイプとして異種配列を作成することを禁止しています。VehicleFuelType

プロのヒント:

エラーの詳細と、Swift 5.7より前のエラーを回避する方法については、Mediumに掲載された私の記事「Swift:PATでの動的ディスパッチの達成(関連付けられたタイプのプロトコル)」を確認してください。

AppleがSwiftコンパイラに対して行ったアップグレードのおかげで、この制限はSwift5.7には存在しなくなりました。最終的に、OOPでスーパークラスを使用するのと同じようにプロトコルを使用できます。その方法をお見せしましょう。

 

単純な関数で動的ディスパッチを実行する

Swift 5.7では、異種配列の作成がコンパイラーによって禁止されなくなりました。anyキーワードを使用するだけです。

// Use `any` to indicate that the array will hold existential type
let vehicles: [any Vehicle] = [
    Car(name: "Car_1"),
    Car(name: "Car_2"),
    Bus(name: "Bus_1"),
    Car(name: "Car_3"),
]

func startAllEngin(for vehicles: [any Vehicle]) {
    for vehicle in vehicles {
        vehicle.startEngin()
    }
}

キーワードを使用することによりany、配列には実存型が含まれ、その基になる具象型は常にVehicleプロトコルに準拠することをコンパイラーに通知します。

これにより、呼び出しにより、startAllEngin(for:)必要な動的ディスパッチが得られます。

startAllEngin(for: vehicles)

// Output:
// Car_1 enjin started!
// Car_2 enjin started!
// Bus_1 enjin started!
// Car_3 enjin started!

 

ジェネリックパラメーターを使用した関数での動的ディスパッチの実行

次に、別のより複雑な例を見てみましょう。。という名前の関数を作成するとしますfillAllGasTank(for:)。この関数は、指定された配列fillGasTank(with:)に基づいて車両の関数への動的ディスパッチを実行します。vehicles

ジェネリックパラメータタイプを定義する

私たちが達成しようとしていることは、最初は簡単に思えるかもしれませんが、コーディングを開始すると、最初の問題にぶつかります。


func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {
        // 🤔 What to pass in here?
        vehicle.fillGasTank(with: ????)
    }
}

車両の種類によって必要な燃料の種類が異なるため、との両方を表す一般的なプロトコルを作成する必要がありGasolineますDiesel。先に進んでそれをしましょう。

protocol Fuel {
   
    // Constrain `FuelType` to always equal to the type that conforms to the `Fuel` protocol
    associatedtype FuelType where FuelType == Self

    static func purchase() -> FuelType
}

プロトコルは、という名前の関連付けられた型と静的関数Fuelで構成される単純なプロトコルです。プロトコルに準拠するタイプと常に等しくなるように制約する方法に注意してください。この制約は、コンパイラが静的関数によって返される具体的な型を判別するために非常に重要です。FuelTypepurchase()FuelTypeFuelpurchase()

次に、プロトコルGasolineとプロトコルの両方に準拠しましょう。DieselFuel


struct Gasoline: Fuel {
    
    let name = "gasoline"
    
    static func purchase() -> Gasoline {
        print("Purchase gasoline from gas station.")
        return Gasoline()
    }
}

struct Diesel: Fuel {
    
    let name = "diesel"
    
    static func purchase() -> Diesel {
        print("Purchase diesel from gas station.")
        return Diesel()
    }
}

Vehicleさらに、プロトコルがプロトコルFuelTypeに準拠するタイプであることを確認する必要もありFuelます。

protocol Vehicle {

    // `FuelType` must be type that conform to the `Fuel` protocol
    associatedtype FuelType: Fuel

    // ...
    // ...
}

「任意」から「一部」への変換

Fuelプロトコルと他のすべての関連する変更が適切に行われたので、関数を再検討し、それに応じて更新することができますfillAllGasTank(for:)

func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {

        // Get the instance of `Fuel` concrete type based on the vehicle's fuel type
        let fuel = type(of: vehicle).FuelType.purchase()

        // 🔴 Compile error: Member 'fillGasTank' cannot be used on value of type 'any Vehicle'; consider using a generic constraint instead
        vehicle.fillGasTank(with: fuel)
    }
}

上記のコードでは、車両の燃料タイプを利用してFuelコンクリートタイプのインスタンスを取得し、それをfillGasTank(with:)関数に渡す方法に注目してください。

残念ながら、コードをコンパイルしようとすると、2番目の問題が発生します。メンバー'fillGasTank'は、タイプ'anyVehicle'の値には使用できません。代わりに一般的な制約を使用することを検討してください。 」どういう意味ですか?

some発生しているエラーを理解するために、とanyキーワードの違いを簡単に要約してみましょう。

Swiftのsomeキーワードとanyキーワードの違いを比較する

上の画像に示されているように、実存型の基礎となるコンクリート型は箱の中に包まれています。したがって、コンパイラはfillGasTank(with:)関数へのアクセスを禁止しています。これを行うには、関数にアクセスする前に、まず実存型を不透明(OPAQUE)型に変換(ボックス解除)する必要がありfillGasTank(with:)ます。

幸い、AppleはSwift 5.7で変換(開梱)プロセスを非常に簡単にしました。不透明(OPAQUE)型を受け入れる関数に実存型を渡すだけで、変換が自動的に行われます。


func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {
        // Pass in `any Vehicle` to convert it to `some Vehicle`
        fillGasTank(for: vehicle)
    }
}

// Create a function that accept `some Vehicle` (opaque type)
func fillGasTank(for vehicle: some Vehicle) {

    let fuel = type(of: vehicle).FuelType.purchase()
    vehicle.fillGasTank(with: fuel)
}

これで、エラーなしでコードをコンパイルして実行できるようになりました。


fillAllGasTank(for: vehicles)

// Output:
// Purchase gasoline from gas station.
// Fill Car_1 with gasoline.
// Purchase gasoline from gas station.
// Fill Car_2 with gasoline.
// Purchase diesel from gas station.
// Fill Bus_1 with diesel.
// Purchase gasoline from gas station.
// Fill Car_3 with gasoline.

自分で試してみたい場合は、ここで完全なサンプルコードを入手してください。

 

まとめ

あります!これが、関連付けられたタイプのプロトコルで動的ディスパッチを実現する方法です。すべてを要約すると、これらすべてを可能にするSwift5.7の改善点は次のとおりです。

  1. 関連付けられたタイプのプロトコルを使用して異種アレイを作成する際の制限を取り除きます。
  2. 関数のパラメーター位置でanyおよびキーワードの使用を有効にします。some
  3. 実存型から不透明(OPAQUE)型への自動変換、およびその逆。

読んでくれてありがとう。 

ソース:https ://betterprogramming.pub/how-to-achieve-dynamic-dispatch-using-generic-protocols-in-swift-5-7-cac664d481e0

#swift 

What is GEEK

Buddha Community

Swift5.7で汎用プロトコルを使用して動的ディスパッチを実現する方法

Swift5.7で汎用プロトコルを使用して動的ディスパッチを実現する方法

動的ディスパッチは、オブジェクト指向プログラミング(OOP)の最も重要なメカニズムの1つです。これは、実行時のポリモーフィズムを可能にするコアメカニズムであり、開発者はコンパイル時ではなく実行時に実行パスを決定するコードを記述できます。

OOPで動的ディスパッチを実現するのは簡単なようですが、プロトコル指向プログラミング(POP)の場合はそうではありません。プロトコルを使用して動的ディスパッチを実行しようとすると、Swiftコンパイラのさまざまな制限により、常に予期しない問題が発生します。

Swift 5.7のリリースにより、これらすべてが歴史になりました。POPの領域で動的ディスパッチを実現することはかつてないほど容易になりました。この記事では、Swift 5.7からどのような改善が得られ、関連するタイプのプロトコルを使用して動的ディスパッチを実現するために何が必要かを調べてみましょう。

ですから、これ以上面倒なことはせずに、すぐに始めましょう!

ノート:

someSwiftのandキーワードに慣れていない場合は、最初に「 Swift5.7の「some」および「any」キーワードを理解する」anyというブログ投稿を読むことを強くお勧めします。

 

ファーストシングスファースト

Swift 5.7の改善点を紹介する前に、この記事全体でサンプルコードに必要なプロトコルと構造体を定義しましょう。

struct Gasoline {
    let name = "gasoline"
}

struct Diesel {
    let name = "diesel"
}

protocol Vehicle {

    associatedtype FuelType
    
    var name: String { get }

    func startEngin()
    func fillGasTank(with fuel: FuelType)
}

struct Car: Vehicle {

    let name: String

    func startEngin() {
        print("\(name) enjin started!")
    }

    func fillGasTank(with fuel: Gasoline) {
        print("Fill \(name) with \(fuel.name)")
    }
}

struct Bus: Vehicle {

    let name: String

    func startEngin() {
        print("\(name) enjin started!")
    }

    func fillGasTank(with fuel: Diesel) {
        print("Fill \(name) with \(fuel.name)")
    }

}

上記の定義は、前回の記事で使用したものと似ていますが、少しひねりがあります。このVehicleプロトコルでは、2つの機能要件がstartEngin()ありfillGasTank(with:)ます。デモンストレーションのために、これら2つの関数と構造体の両方を使用して動的ディスパッチを実現しようとしCarますBus

 

Swift5.6以下の汎用プロトコルの制限

ここで、startAllEngin()以下に示すように、異種配列を受け入れる関数を作成するとします。

// Heterogeneous array with `Car` and `Bus` elements
// 🔴 Compile error: Protocol ‘Vehicle’ can only be used as a generic constraint because it has Self or associated type requirements
let vehicles: [Vehicle] = [
    Car(name: "Car_1"),
    Car(name: "Car_2"),
    Bus(name: "Bus_1"),
    Car(name: "Car_3"),
]

func startAllEngin(for vehicles: [Vehicle]) {
    for vehicle in vehicles {
        vehicle.startEngin()
    }
}

// Execution
startAllEngin(for: vehicles)

Swift 5.6では、「プロトコル'Vehicle'は、Selfまたは関連するタイプ要件があるため、ジェネリック制約としてのみ使用できます」というエラーが表示されるため、これは文字通り不可能であることに気付くでしょう。Swiftコンパイラは、タイプ()が関連付けられているVehicleため、要素タイプとして異種配列を作成することを禁止しています。VehicleFuelType

プロのヒント:

エラーの詳細と、Swift 5.7より前のエラーを回避する方法については、Mediumに掲載された私の記事「Swift:PATでの動的ディスパッチの達成(関連付けられたタイプのプロトコル)」を確認してください。

AppleがSwiftコンパイラに対して行ったアップグレードのおかげで、この制限はSwift5.7には存在しなくなりました。最終的に、OOPでスーパークラスを使用するのと同じようにプロトコルを使用できます。その方法をお見せしましょう。

 

単純な関数で動的ディスパッチを実行する

Swift 5.7では、異種配列の作成がコンパイラーによって禁止されなくなりました。anyキーワードを使用するだけです。

// Use `any` to indicate that the array will hold existential type
let vehicles: [any Vehicle] = [
    Car(name: "Car_1"),
    Car(name: "Car_2"),
    Bus(name: "Bus_1"),
    Car(name: "Car_3"),
]

func startAllEngin(for vehicles: [any Vehicle]) {
    for vehicle in vehicles {
        vehicle.startEngin()
    }
}

キーワードを使用することによりany、配列には実存型が含まれ、その基になる具象型は常にVehicleプロトコルに準拠することをコンパイラーに通知します。

これにより、呼び出しにより、startAllEngin(for:)必要な動的ディスパッチが得られます。

startAllEngin(for: vehicles)

// Output:
// Car_1 enjin started!
// Car_2 enjin started!
// Bus_1 enjin started!
// Car_3 enjin started!

 

ジェネリックパラメーターを使用した関数での動的ディスパッチの実行

次に、別のより複雑な例を見てみましょう。。という名前の関数を作成するとしますfillAllGasTank(for:)。この関数は、指定された配列fillGasTank(with:)に基づいて車両の関数への動的ディスパッチを実行します。vehicles

ジェネリックパラメータタイプを定義する

私たちが達成しようとしていることは、最初は簡単に思えるかもしれませんが、コーディングを開始すると、最初の問題にぶつかります。


func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {
        // 🤔 What to pass in here?
        vehicle.fillGasTank(with: ????)
    }
}

車両の種類によって必要な燃料の種類が異なるため、との両方を表す一般的なプロトコルを作成する必要がありGasolineますDiesel。先に進んでそれをしましょう。

protocol Fuel {
   
    // Constrain `FuelType` to always equal to the type that conforms to the `Fuel` protocol
    associatedtype FuelType where FuelType == Self

    static func purchase() -> FuelType
}

プロトコルは、という名前の関連付けられた型と静的関数Fuelで構成される単純なプロトコルです。プロトコルに準拠するタイプと常に等しくなるように制約する方法に注意してください。この制約は、コンパイラが静的関数によって返される具体的な型を判別するために非常に重要です。FuelTypepurchase()FuelTypeFuelpurchase()

次に、プロトコルGasolineとプロトコルの両方に準拠しましょう。DieselFuel


struct Gasoline: Fuel {
    
    let name = "gasoline"
    
    static func purchase() -> Gasoline {
        print("Purchase gasoline from gas station.")
        return Gasoline()
    }
}

struct Diesel: Fuel {
    
    let name = "diesel"
    
    static func purchase() -> Diesel {
        print("Purchase diesel from gas station.")
        return Diesel()
    }
}

Vehicleさらに、プロトコルがプロトコルFuelTypeに準拠するタイプであることを確認する必要もありFuelます。

protocol Vehicle {

    // `FuelType` must be type that conform to the `Fuel` protocol
    associatedtype FuelType: Fuel

    // ...
    // ...
}

「任意」から「一部」への変換

Fuelプロトコルと他のすべての関連する変更が適切に行われたので、関数を再検討し、それに応じて更新することができますfillAllGasTank(for:)

func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {

        // Get the instance of `Fuel` concrete type based on the vehicle's fuel type
        let fuel = type(of: vehicle).FuelType.purchase()

        // 🔴 Compile error: Member 'fillGasTank' cannot be used on value of type 'any Vehicle'; consider using a generic constraint instead
        vehicle.fillGasTank(with: fuel)
    }
}

上記のコードでは、車両の燃料タイプを利用してFuelコンクリートタイプのインスタンスを取得し、それをfillGasTank(with:)関数に渡す方法に注目してください。

残念ながら、コードをコンパイルしようとすると、2番目の問題が発生します。メンバー'fillGasTank'は、タイプ'anyVehicle'の値には使用できません。代わりに一般的な制約を使用することを検討してください。 」どういう意味ですか?

some発生しているエラーを理解するために、とanyキーワードの違いを簡単に要約してみましょう。

Swiftのsomeキーワードとanyキーワードの違いを比較する

上の画像に示されているように、実存型の基礎となるコンクリート型は箱の中に包まれています。したがって、コンパイラはfillGasTank(with:)関数へのアクセスを禁止しています。これを行うには、関数にアクセスする前に、まず実存型を不透明(OPAQUE)型に変換(ボックス解除)する必要がありfillGasTank(with:)ます。

幸い、AppleはSwift 5.7で変換(開梱)プロセスを非常に簡単にしました。不透明(OPAQUE)型を受け入れる関数に実存型を渡すだけで、変換が自動的に行われます。


func fillAllGasTank(for vehicles: [any Vehicle]) {

    for vehicle in vehicles {
        // Pass in `any Vehicle` to convert it to `some Vehicle`
        fillGasTank(for: vehicle)
    }
}

// Create a function that accept `some Vehicle` (opaque type)
func fillGasTank(for vehicle: some Vehicle) {

    let fuel = type(of: vehicle).FuelType.purchase()
    vehicle.fillGasTank(with: fuel)
}

これで、エラーなしでコードをコンパイルして実行できるようになりました。


fillAllGasTank(for: vehicles)

// Output:
// Purchase gasoline from gas station.
// Fill Car_1 with gasoline.
// Purchase gasoline from gas station.
// Fill Car_2 with gasoline.
// Purchase diesel from gas station.
// Fill Bus_1 with diesel.
// Purchase gasoline from gas station.
// Fill Car_3 with gasoline.

自分で試してみたい場合は、ここで完全なサンプルコードを入手してください。

 

まとめ

あります!これが、関連付けられたタイプのプロトコルで動的ディスパッチを実現する方法です。すべてを要約すると、これらすべてを可能にするSwift5.7の改善点は次のとおりです。

  1. 関連付けられたタイプのプロトコルを使用して異種アレイを作成する際の制限を取り除きます。
  2. 関数のパラメーター位置でanyおよびキーワードの使用を有効にします。some
  3. 実存型から不透明(OPAQUE)型への自動変換、およびその逆。

読んでくれてありがとう。 

ソース:https ://betterprogramming.pub/how-to-achieve-dynamic-dispatch-using-generic-protocols-in-swift-5-7-cac664d481e0

#swift 

坂本  篤司

坂本 篤司

1655982000

Swift5.7の「Some」および「Any」のキーワードを理解する

およびキーワードはSwiftでは新しいものではありませんsome。キーワードはSwift5.1で導入されましたが、anyキーワードはSwift5.6で導入されました。Swift 5.7では、Appleはこれらのキーワードの両方にもう1つの大きな改善を加えています。これで、これらのキーワードの両方を関数のパラメーター位置で使用できるようになりました。someany

func doSomething(with a: some MyProtocol) {    // Do something}func doSomething(with a: any MyProtocol) {    // Do something    }

この改善により、ジェネリック関数の見た目がすっきりするだけでなく、Swiftでジェネリックコードを記述するためのいくつかのエキサイティングな新しい方法が解き放たれました。ネタバレ注意—次のエラーメッセージに別れを告げることができます。

プロトコルには自己または関連するタイプ要件があるため、プロトコルはジェネリック制約としてのみ使用できます

もっと知りたい?読む!

ファーストシングスファースト

詳細に入る前に、この記事全体で使用するプロトコルを定義しましょう。

protocol Vehicle {    var name: String { get }    associatedtype FuelType    func fillGasTank(with fuel: FuelType)}

その後、プロトコルに準拠Carする構造体と構造体を定義し、それぞれに異なる種類の燃料が必要になります。コードは次のとおりです。BusVehicle

struct Car: Vehicle {    let name = "car"    func fillGasTank(with fuel: Gasoline) {        print("Fill \(name) with \(fuel.name)")    }}struct Bus: Vehicle {    let name = "bus"    func fillGasTank(with fuel: Diesel) {        print("Fill \(name) with \(fuel.name)")    }}struct Gasoline {    let name = "gasoline"}struct Diesel {    let name = "diesel"}

fillGasTank(with:)関数のパラメータデータ型CarBusは同じではないことに注意してください。requiresに対してCarrequires 。そのため、プロトコルで呼び出される関連タイプを定義する必要があります。GasolineBusDieselFuelTypeVehicle

それが邪魔にならないように、詳細に飛び込みましょう。

「いくつかの」キーワードを理解する

このsomeキーワードはSwift5.1で導入されました。これはプロトコルと一緒に使用され、特定のプロトコルに準拠しているものを表す不透明(OPAQUE)型を作成します。関数のパラメーター位置で使用される場合、それは、関数が特定のプロトコルに準拠する具体的なタイプを受け入れることを意味します。

この段階で、あなたは不思議に思うかもしれません、私たちはすでにそれをすることができませんか?

実際、あなたは正しいです。関数のパラメーター位置でキーワードを使用することは、関数のシグニチャーでsome山括弧または末尾のwhere句を使用することとまったく同じです。

// The following 3 function signatures are identical.func wash<T: Vehicle>(_ vehicle: T) {    // Wash the given vehicle}func wash<T>(_ vehicle: T) where T: Vehicle {    // Wash the given vehicle}func wash(_ vehicle: some Vehicle)  {    // Wash the given vehicle}

変数でキーワードを使用するsomeと、特定の具象型で作業していることをコンパイラーに通知するため、不透明(OPAQUE)型の基になる型を変数のスコープに固定する必要があります。

var myCar: some Vehicle = Car()myCar = Bus() // 🔴 Compile error: Cannot assign value of type 'Bus' to type 'some Vehicle'

注意すべき興味深い点の1つは、同じ具象型の新しいインスタンスを変数に割り当てることもコンパイラーによって禁止されていることです。

var myCar: some Vehicle = Car()myCar = Car() // 🔴 Compile error: Cannot assign value of type 'Car' to type 'some Vehicle'var myCar1: some Vehicle = Car()var myCar2: some Vehicle = Car()myCar2 = myCar1 // 🔴 Compile error: Cannot assign value of type 'some Vehicle' (type of 'myCar1') to type 'some Vehicle' (type of 'myCar2')

そのことを念頭に置いて、配列で使用する場合も同じルールに従う必要があります。

// ✅ No compile errorlet vehicles: [some Vehicle] = [    Car(),    Car(),    Car(),]// 🔴 Compile error: Cannot convert value of type 'Bus' to expected element type 'Car'let vehicles: [some Vehicle] = [    Car(),    Car(),    Bus(),]

同じことが、関数の基になる戻り値タイプにも当てはまります。

// ✅ No compile errorfunc createSomeVehicle() -> some Vehicle {    return Car()}// 🔴 Compile error: Function declares an opaque return type 'some Vehicle', but the return statements in its body do not have matching underlying typesfunc createSomeVehicle(isPublicTransport: Bool) -> some Vehicle {    if isPublicTransport {        return Bus()    } else {        return Car()    }}

someキーワードは以上です。anyキーワードに向かい、それらの違いを見てみましょう。

「any」キーワードを理解する

このanyキーワードはSwift5.6で導入されました。実存型を作成する目的で導入されています。Swift 5.6では、any実存型を作成するときにキーワードは必須ではありませんが、Swift 5.7では、そうしなかった場合にコンパイルエラーが発生します。

let myCar: Vehicle = Car() // 🔴 Compile error in Swift 5.7: Use of protocol 'Vehicle' as a type must be written 'any Vehicle' let myCar: any Vehicle = Car() // ✅ No compile error in Swift 5.7// 🔴 Compile error in Swift 5.7: Use of protocol 'Vehicle' as a type must be written 'any Vehicle' func wash(_ vehicle: Vehicle)  {    // Wash the given vehicle}// ✅ No compile error in Swift 5.7func wash(_ vehicle: any Vehicle)  {    // Wash the given vehicle}

Appleのエンジニアが説明したように、実存型は特定のプロトコルに準拠するものを含むボックスのようなものです。

一部のキーワードと任意のキーワードの比較

上の画像に示されているように、不透明(OPAQUE)型と実存型の主な違いは「ボックス」です。「ボックス」を使用すると、基になる型が指定されたプロトコルに準拠している限り、その中に具体的な型を格納できます。したがって、不透明(OPAQUE)型では実行できないことを実行できます。

// ✅ No compile error when changing the underlying data typevar myCar: any Vehicle = Car()myCar = Bus()myCar = Car()// ✅ No compile error when returning different kind of concrete type func createAnyVehicle(isPublicTransport: Bool) -> any Vehicle {    if isPublicTransport {        return Bus()    } else {        return Car()    }}

最良の部分は、Swift 5.7で、any関連付けられたタイプのプロトコルにキーワードを使用できるようになったことです。これは、関連付けられたタイプのプロトコルを使用して異種配列を作成することは、もはや制限ではないことを意味します。

// 🔴 Compile error in Swift 5.6: protocol 'Vehicle' can only be used as a generic constraint because it has Self or associated type requirements// ✅ No compile error in Swift 5.7let vehicles: [any Vehicle] = [    Car(),    Car(),    Bus(),]

それはどれくらいクールですか?😃

この改善により、「プロトコルには自己または関連する型の要件があるため、プロトコルはジェネリック制約としてのみ使用できる」というエラーが排除されただけでなく、関連する型を持つプロトコルでの動的ディスパッチの実行がはるかに簡単になります。しかし、それは別の日の記事になります。

「任意の」キーワードの制限

見た目は良いかもしれませんが、anyキーワードを使用して作成された実存型には、まだ独自の制限があります。==主な制限の1つは、演算子を使用して実存型の2つのインスタンスを比較できないことです。

// Conform `Vehicle` protocol to `Equatable`protocol Vehicle: Equatable {    var name: String { get }    associatedtype FuelType    func fillGasTank(with fuel: FuelType)}let myCar1 = createAnyVehicle(isPublicTransport: false)let myCar2 = createAnyVehicle(isPublicTransport: false)let isSameVehicle = myCar1 == myCar2 // 🔴 Compile error: Binary operator '==' cannot be applied to two 'any Vehicle' operandslet myCar1 = createSomeVehicle()let myCar2 = createSomeVehicle()let isSameVehicle = myCar1 == myCar2 // ✅ No compile error

あなたがそれについて考えるならば、これは実際に一種の理にかなっています。前述のように、実存型は、その「ボックス」に格納された任意の具象型を持つことができます。コンパイラにとって、実存型は単なる「ボックス」であり、ボックスの中に何が入っているのかわかりません。したがって、「ボックス」の内容が同じ基本的な具象型であることを保証できない場合、コンパイラーは比較を行うことができません。

注意すべきもう1つの制限は、実存型は不透明(OPAQUE)型(someキーワードを使用して作成)よりも効率が低いことです。Donny Walsには、これについて詳しく説明しているすばらしい記事があります。ぜひチェックしてみてください。

したがって、Appleはキーワードに多くの改良を加えましたが、不透明(OPAQUE)型で作業を完了できる場合は、キーワードanyを使用することをお勧めします。some

出典:Swiftの新機能

まとめ

anySwift5.7のandsomeキーワードの改善は間違いなく歓迎すべきものです。一方では、ジェネリックコードの構文と読みやすさが大幅に向上しました。一方で、それは私たちがはるかに効率的な方法で一般的なコードを書くための新しい方法を開きます。

anyこの記事がandsomeキーワードをよく調べてくれることを願っています。

読んでくれてありがとう。  

出典:https ://betterprogramming.pub/understanding-the-some-and-any-keywords-in-swift-5-7-19d2cb52eae2

#swift 

渚 あ すか

渚 あ すか

1623719806

React18α | swift5.5 | Xcodeクラウド | Deno 1.11 | Terraform 1.0 | Google Open Source Insight 【エンジニアニュース】

React18α | swift5.5 | Xcodeクラウド | Deno 1.11 | Terraform 1.0 | Google Open Source Insight 【エンジニアニュース】

#react #swift #xcode #deno