web-dev-qa-db-ja.com

Swiftプロトコル拡張機能のオーバーライド

Swiftプロトコル拡張機能を試していますが、これは非常に紛らわしい動作であることがわかりました。希望する結果を得る方法を手伝ってもらえますか?

コードの最後の4行のコメントを参照してください。 (必要に応じて、コピーしてXcode7プレイグラウンドに貼り付けることができます)。ありがとうございました!!

//: Playground - noun: a place where people can play

import UIKit

protocol Color { }
extension Color {  var color : String { return "Default color" } }

protocol RedColor: Color { }
extension RedColor { var color : String { return "Red color" } }


protocol PrintColor {

     func getColor() -> String
}

extension PrintColor where Self: Color {

    func getColor() -> String {

        return color
    }
}


class A: Color, PrintColor { }
class B: A, RedColor { }


let colorA = A().color // is "Default color" - OK
let colorB = B().color // is "Red color" - OK


let a = A().getColor() // is "Default color" - OK
let b = B().getColor() // is "Default color" BUT I want it to be "Red color"
48
VojtaStavik

簡単な答えは、プロトコル拡張機能はクラスのポリモーフィズムを実行しないということです。これは、プロトコルを構造体または列挙型で採用できるため、また、プロトコルを採用するだけで、必要のない場所に動的なディスパッチを導入したくないため、特定の意味があります。

したがって、getColor()では、colorインスタンス変数(より正確にself.color)は、クラスがポリモーフィックに考えており、プロトコルはそうではないので、あなたが考えるとは限りません。だからこれは動作します:

let colorB = B().color // is "Red color" - OK

... colorを解決するためにclassを要求しているが、これは期待したことをしない:

let b = B().getColor() // is "Default color" BUT I want it to be "Red color"

... getColorメソッドは完全にプロトコル拡張で定義されているためです。 BでgetColorを再定義することで問題を修正できます。

class B: A, RedColor {
    func getColor() -> String {
        return self.color
    }
}

これで、クラスのgetColorが呼び出され、selfが何であるかを多態的に認識します。

47
matt

colorColorを定義し、Bの実装リストを切り替えることで、それを機能させることができました。BAでなければならない場合はあまり良くありません。

protocol Color {
    var color : String { get }
}

protocol RedColor: Color {

}

extension Color {
    var color : String {
        get {return "Default color"}
    }
}

extension RedColor {
    var color : String {
        get {return "Red color"}
    }
}

protocol PrintColor {
    func getColor() -> String
}

extension PrintColor where Self: Color {
    func getColor() -> String {
        return color
    }
}

class A : Color, PrintColor {

}

class B : RedColor, PrintColor {

}

let a = A().getColor() // "Default color"
let b = B().getColor() // "Red color"
4
Ian Warburton

ここでは、2つの非常に異なる問題があります。プロトコルの動的な動作と、プロトコルの「デフォルト」実装の解決です。

  1. 動的な面では、簡単な例で問題を説明できます。

    protocol Color { }
    
    extension Color {
        var color: String { return "Default color" }
    }
    
    class BlueBerry: Color {
        var color: String { return "Blue color" }
    }
    
    let berry = BlueBerry()
    print("\(berry.color)")                 // prints "Blue color", as expected
    
    let colorfulThing: Color = BlueBerry()
    print("\(colorfulThing.color)")         // prints "Default color"!
    

    答え で指摘したように、元のcolorプロトコルの一部としてColorを定義すると、動的な動作を得ることができます(つまり、コンパイラーに合理的に指示します)適合クラスがこのメソッドを実装し、何も見つからない場合にのみプロトコルの実装を使用することを期待してください):

    protocol Color {
        var color: String { get }
    }
    
    ...
    
    let colorfulThing: Color = BlueBerry()
    print("\(colorfulThing.color)")         // now prints "Blue color", as expected
    
  2. さて、 答え で、BAのサブクラスであるときに、なぜこれが少しバラバラになるのか疑問に思います。

    プロトコル拡張のメソッド実装は「デフォルト」実装、つまり、準拠クラスがそれ自体を実装しない場合に使用される実装であることを覚えておくと役立つと思います。あなたの場合の混乱の原因は、BRedColorのデフォルト実装を持つcolorに準拠しているが、BAのサブクラス。Colorのデフォルト実装が異なるcolorに準拠しています。

    したがって、Swiftがこの状況を処理することについて口論するかもしれません(個人的には、この本質的に曖昧な状況について警告を見たいと思います)が、問題の根本は、2つの異なる階層(OOPサブクラスのオブジェクト階層とプロトコル継承のPOPプロトコル階層)と、これにより2つの競合する「デフォルト」実装が行われます。

私はこれが古い質問であることを知っているので、あなたはおそらく他のことに移ってからずっと長いことでしょう。ただし、このコードをリファクタリングする正しい方法についてまだ苦労している場合は、このクラス階層とこのプロトコル継承が実際に表すものについて少し共有してください。より具体的な助言を提供できる場合があります。これは、抽象的な例が問題をさらに混乱させるようなケースの1つです。タイプ/プロトコルが実際に何であるかを見てみましょう。 (コードが機能している場合は、 http://codereview.stackexchange.com が最適な場所です。)

4
Rob

プロトコルを介して「オプション」メソッドを実装しようとしたときに、この問題に遭遇しました。 canは、構造体、継承しないクラス、およびオーバーライド可能な非プロトコルデフォルトメソッドを実装するベースから継承するクラスで機能するようになります。動作しない唯一のケースは、適合を宣言するが、独自の「デフォルトではない」実装を提供しないベースから継承するクラスです。その場合、プロトコル拡張のデフォルトはベースクラスに「ベイクイン」されます、オーバーライドまたは再定義することはできません。

簡単な例:

typealias MyFunction = () -> ()
protocol OptionalMethod {
    func optionalMethod() -> MyFunction?
    func executeOptionalMethod()
}
extension OptionalMethod {
    func optionalMethod() -> MyFunction? { return nil }
    func executeOptionalMethod() {
        if let myFunc = self.optionalMethod() {
            myFunc()
        } else {
            print("Type \(self) has not implemented `optionalMethod`")
        }
    }
}

class A: OptionalMethod {
}
class B: A {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optional method") }
    }
}
struct C: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}
class D: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}
class E: D {
    override func optionalMethod() -> MyFunction? {
        return { print("Hello DIFFERENT optionalMethod") }
    }
}
/* Attempt to get B to declare its own conformance gives:
// error: redundant conformance of 'B2' to protocol 'OptionalMethod'
class B2: A, OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optional method") }
    }
}
*/
class A2: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return nil
    }
}
class B2: A2 {
    override func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}

let a = A() // Class A doesn't implement & therefore defaults to protocol extension implementation
a.executeOptionalMethod() // Type __lldb_expr_201.A has not implemented `optionalMethod`
let b = B() // Class B implements its own, but "inherits" implementation from superclass A
b.executeOptionalMethod() // Type __lldb_expr_205.B has not implemented `optionalMethod`
let c = C() // Struct C implements its own, and works
c.executeOptionalMethod() // Hello optionalMethod
let d = D() // Class D implements its own, inherits from nothing, and works
d.executeOptionalMethod() // Hello optionalMethod
let e = E() // Class E inherits from D, but overrides, and works
e.executeOptionalMethod() // Hello DIFFERENT optionalMethod
let a2 = A2() // Class A2 implements the method, but returns nil, (equivalent to A)
a2.executeOptionalMethod() // Type __lldb_expr_334.A2 has not implemented `optionalMethod`
let b2 = B2() // Class B2 overrides A2's "nil" implementation, and so works
b2.executeOptionalMethod() // Hello optionalMethod
0
Grimxn

注:提案されたソリューション「元のcolorプロトコルの一部としてColorを定義する」は、継承が関係している場合、問題を解決していません。 RedBerryは、プロトコルBlueBerryに準拠するColorから継承します。

protocol Color {
    var color: String { get }
}

extension Color {
    var color: String { return "Default color" }
}

class BlueBerry: Color {
    //    var color: String { return "Blue color" }
}

class RedBerry: BlueBerry {
    var color: String { return "Red color" }
}

let berry = RedBerry()
print(berry.color)             // Red color

let colorfulThing: Color = RedBerry()
print(colorfulThing.color)     // Actual: Default color, Expected: Red color
0
Grand M