1657155600
動的ディスパッチは、オブジェクト指向プログラミング(OOP)の最も重要なメカニズムの1つです。これは、実行時のポリモーフィズムを可能にするコアメカニズムであり、開発者はコンパイル時ではなく実行時に実行パスを決定するコードを記述できます。
OOPで動的ディスパッチを実現するのは簡単なようですが、プロトコル指向プログラミング(POP)の場合はそうではありません。プロトコルを使用して動的ディスパッチを実行しようとすると、Swiftコンパイラのさまざまな制限により、常に予期しない問題が発生します。
Swift 5.7のリリースにより、これらすべてが歴史になりました。POPの領域で動的ディスパッチを実現することはかつてないほど容易になりました。この記事では、Swift 5.7からどのような改善が得られ、関連するタイプのプロトコルを使用して動的ディスパッチを実現するために何が必要かを調べてみましょう。
ですから、これ以上面倒なことはせずに、すぐに始めましょう!
ノート:
some
Swiftの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
キーワードの違いを簡単に要約してみましょう。
上の画像に示されているように、実存型の基礎となるコンクリート型は箱の中に包まれています。したがって、コンパイラは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の改善点は次のとおりです。
any
およびキーワードの使用を有効にします。some
読んでくれてありがとう。
1657155600
動的ディスパッチは、オブジェクト指向プログラミング(OOP)の最も重要なメカニズムの1つです。これは、実行時のポリモーフィズムを可能にするコアメカニズムであり、開発者はコンパイル時ではなく実行時に実行パスを決定するコードを記述できます。
OOPで動的ディスパッチを実現するのは簡単なようですが、プロトコル指向プログラミング(POP)の場合はそうではありません。プロトコルを使用して動的ディスパッチを実行しようとすると、Swiftコンパイラのさまざまな制限により、常に予期しない問題が発生します。
Swift 5.7のリリースにより、これらすべてが歴史になりました。POPの領域で動的ディスパッチを実現することはかつてないほど容易になりました。この記事では、Swift 5.7からどのような改善が得られ、関連するタイプのプロトコルを使用して動的ディスパッチを実現するために何が必要かを調べてみましょう。
ですから、これ以上面倒なことはせずに、すぐに始めましょう!
ノート:
some
Swiftの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
キーワードの違いを簡単に要約してみましょう。
上の画像に示されているように、実存型の基礎となるコンクリート型は箱の中に包まれています。したがって、コンパイラは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の改善点は次のとおりです。
any
およびキーワードの使用を有効にします。some
読んでくれてありがとう。
1655982000
およびキーワードは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:)
関数のパラメータデータ型Car
とBus
は同じではないことに注意してください。requiresに対してCar
requires 。そのため、プロトコルで呼び出される関連タイプを定義する必要があります。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の新機能
まとめ
any
Swift5.7のandsome
キーワードの改善は間違いなく歓迎すべきものです。一方では、ジェネリックコードの構文と読みやすさが大幅に向上しました。一方で、それは私たちがはるかに効率的な方法で一般的なコードを書くための新しい方法を開きます。
any
この記事がandsome
キーワードをよく調べてくれることを願っています。
読んでくれてありがとう。
出典:https ://betterprogramming.pub/understanding-the-some-and-any-keywords-in-swift-5-7-19d2cb52eae2
1623719806
React18α | swift5.5 | Xcodeクラウド | Deno 1.11 | Terraform 1.0 | Google Open Source Insight 【エンジニアニュース】
#react #swift #xcode #deno