Попытка выяснить странные результаты протокола в Swift - PullRequest
0 голосов
/ 11 апреля 2020

Вот сделка. Я сталкивался с таким поведением в моем программировании реализации, и это действительно озадачило меня. Я пытаюсь выяснить, почему это происходит, поэтому я могу объяснить это в будущем.

Я вставлю довольно сложную (но минимальную) площадку через минуту, но я хотел обрисовать проблему , во-первых.

Проблема в том, что, если я определяю класс (не структуру) как соответствующий протоколу с реализацией по умолчанию, а затем переопределяю это путем реализации определенного метода, реализация протокола по умолчанию продолжает выполняться , даже когда я ожидал, что вторичная реализация будет выполнена, а иерархии наследования будут игнорироваться.

Ниже у меня есть игровая площадка (последний Xcode) и полученная распечатка.

ЧТО Я ОЖИДАЮ

Все должно следовать примеру, установленному прямым вызовом метода (1). Это включает в себя пару откатов к значениям по умолчанию протокола.

ЧТО НА СЛУЧАЕ ПРОИСХОДИТ

Реализация по умолчанию в протоколах (из которой происходят классы / которой соответствуют классы) ) продолжает выполняться.

Наиболее удивительными являются (6) - (8).

(2) - (4) также немного странны, так как D и E (и Q и R) печататься правильно, но ни один из других не делает.

Кто-нибудь имеет какие-либо идеи, почему я могу получить это поведение?

ИЕРАРХИЯ КАРТА

Pseudo-UML Diagram of the Hierarchy

игровая площадка:

// MARK: - Test Functions

// Argument is Class A
func printValueFromClassA(_ inValueAsClassA: A) {
    inValueAsClassA.printValue()
}

// Argument is Class H
func printValueFromClassH(_ inValueAsClassH: H) {
    inValueAsClassH.printValue()
}

// Argument is Class J
func printValueFromClassJ(_ inValueAsClassJ: J) {
    inValueAsClassJ.printValue()
}

// Argument is Class D
func printValueFromClassD(_ inValueAsClassD: D) {
    inValueAsClassD.printValue()
}

// Argument is the Root protocol
func printValueFromProtocolA(_ inValueAsProtocolA: PA) {
    inValueAsProtocolA.printValue()
}

// Argument is the first derived protocol
func printValueFromProtocolB(_ inValueAsProtocolB: PB) {
    inValueAsProtocolB.printValue()
}

// Argument is the second derived protocol
func printValueFromProtocolC(_ inValueAsProtocolC: PC) {
    inValueAsProtocolC.printValue()
}

// MARK: - Protocols

// Root protocol: Defines the method
protocol PA {
    func printValue()
}

// Root protocol default implementation
extension PA {
    func printValue() { print("\t\tProtocol A") }
}

// Derived protocol: Depends on Root protocol default
protocol PB: PA { }

// Derived protocol: defines a new default implementation
protocol PC: PB { }

// Deived protocol default implementation
extension PC {
    func printValue() { print("\t\tProtocol C") }
}

// MARK: - Classes

// Direct conformance to PA; relying on the default implementation of the conforming method
class A: PA { }

// Derives from A, but implements the conforming method
class B: A {
    func printValue() { print("\t\tClass B") }
}

// Derives from B, and overrides the conforming method
class C: B {
    override func printValue() { print("\t\tClass C") }
}

// Direct conformance to PB (which is based on PA), and implements the conforming method
class D: PB {
    func printValue() { print("\t\tClass D") }
}

// Derives from D, and overrides the conforming method
class E: D {
    override func printValue() { print("\t\tClass E") }
}

// Direct conformance to PA, but defines the conforming method
class F: A {
    func printValue() { print("\t\tClass F") }
}

// Derives from C, and overrides the conforming method (second override)
class G: C {
    override func printValue() { print("\t\tClass G") }
}

// Direct conformance to PB, but relies on the default implementation of the conforming method
class H: PB { }

// Derives from H, but implements the conforming method.
class I: H {
    func printValue() { print("\t\tClass I") }
}

// Direct conformance to the second derived protocol, and relies on that protocol's default implementation
class J: PC { }

// Derived from J, but implements the conforming method
class K: J {
    func printValue() { print("\t\tClass K") }
}

// Derived from J, and overrides the method
class L: K {
    override func printValue() { print("\t\tClass L") }
}

// Derived from F, and overrides the method
class M: F {
    override func printValue() { print("\t\tClass M") }
}

// Derived from I, and overrides the method
class N: I {
    override func printValue() { print("\t\tClass N") }
}

// Derived from N, and overrides the method
class O: N {
    override func printValue() { print("\t\tClass O") }
}

// Derived from L, and overrides the method
class P: L {
    override func printValue() { print("\t\tClass P") }
}

// Direct conformance to the second derived protocol, but implements the conforming method
class Q: PC {
    func printValue() { print("\t\tClass Q") }
}

// Derived from Q, and overrides the method
class R: Q {
    override func printValue() { print("\t\tClass R") }
}

// MARK: - Testing

// MARK: instance definitions
let instanceOfA = A()
let instanceOfB = B()
let instanceOfC = C()
let instanceOfD = D()
let instanceOfE = E()
let instanceOfF = F()
let instanceOfG = G()
let instanceOfH = H()
let instanceOfI = I()
let instanceOfJ = J()
let instanceOfK = K()
let instanceOfL = L()
let instanceOfM = M()
let instanceOfN = N()
let instanceOfO = O()
let instanceOfP = P()
let instanceOfQ = Q()
let instanceOfR = R()


// MARK: Directly calling the instance methods
print("1) Direct Method Call:")
print("\tClass A:")
instanceOfA.printValue()                // "Protocol A"
print("\tClass B:")
instanceOfB.printValue()                // "Class B"
print("\tClass C:")
instanceOfC.printValue()                // "Class C"
print("\tClass D:")
instanceOfD.printValue()                // "Class D"
print("\tClass E:")
instanceOfE.printValue()                // "Class E"
print("\tClass F:")
instanceOfF.printValue()                // "Class F"
print("\tClass G:")
instanceOfG.printValue()                // "Class G"
print("\tClass H:")
instanceOfH.printValue()                // "Protocol A"
print("\tClass I:")
instanceOfI.printValue()                // "Class I"
print("\tClass J:")
instanceOfJ.printValue()                // "Protocol C"
print("\tClass K:")
instanceOfK.printValue()                // "Class K"
print("\tClass L:")
instanceOfL.printValue()                // "Class L"
print("\tClass M:")
instanceOfM.printValue()                // "Class M"
print("\tClass N:")
instanceOfN.printValue()                // "Class N"
print("\tClass O:")
instanceOfO.printValue()                // "Class O"
print("\tClass P:")
instanceOfP.printValue()                // "Class P"
print("\tClass Q:")
instanceOfQ.printValue()                // "Class Q"
print("\tClass R:")
instanceOfR.printValue()                // "Class R"

// MARK: Calling via a function that requires the argument be the Root protocol
print("\n2) printValueFromProtocolA(_: PA):")
print("\tClass A:")
printValueFromProtocolA(instanceOfA)    // "Protocol A"
print("\tClass B:")
printValueFromProtocolA(instanceOfB)    // "Protocol A"
print("\tClass C:")
printValueFromProtocolA(instanceOfC)    // "Protocol A"
print("\tClass D:")
printValueFromProtocolA(instanceOfD)    // "Class D"
print("\tClass E:")
printValueFromProtocolA(instanceOfE)    // "Class E"
print("\tClass F:")
printValueFromProtocolA(instanceOfF)    // "Protocol A"
print("\tClass G:")
printValueFromProtocolA(instanceOfG)    // "Protocol A"
print("\tClass H:")
printValueFromProtocolA(instanceOfH)    // "Protocol A"
print("\tClass I:")
printValueFromProtocolA(instanceOfI)    // "Protocol A"
print("\tClass J:")
printValueFromProtocolA(instanceOfJ)    // "Protocol C"
print("\tClass K:")
printValueFromProtocolA(instanceOfK)    // "Protocol C"
print("\tClass L:")
printValueFromProtocolA(instanceOfL)    // "Protocol C"
print("\tClass M:")
printValueFromProtocolA(instanceOfM)    // "Protocol A"
print("\tClass N:")
printValueFromProtocolA(instanceOfN)    // "Protocol A"
print("\tClass O:")
printValueFromProtocolA(instanceOfO)    // "Protocol A"
print("\tClass P:")
printValueFromProtocolA(instanceOfP)    // "Protocol A"
print("\tClass Q:")
printValueFromProtocolA(instanceOfQ)    // "Class Q"
print("\tClass R:")
printValueFromProtocolA(instanceOfR)    // "Class R"

// MARK: Calling via a function that requires the argument be the first Derived protocol
print("\n3) printValueFromProtocolB(_: PB):")
print("\tClass D:")
printValueFromProtocolB(instanceOfD)    // "Class D"
print("\tClass E:")
printValueFromProtocolB(instanceOfE)    // "Class E"
print("\tClass H:")
printValueFromProtocolB(instanceOfH)    // "Protocol A"
print("\tClass I:")
printValueFromProtocolB(instanceOfI)    // "Protocol A"
print("\tClass J:")
printValueFromProtocolB(instanceOfJ)    // "Protocol C"
print("\tClass K:")
printValueFromProtocolB(instanceOfK)    // "Protocol C"
print("\tClass L:")
printValueFromProtocolB(instanceOfL)    // "Protocol C"
print("\tClass N:")
printValueFromProtocolB(instanceOfN)    // "Protocol A"
print("\tClass O:")
printValueFromProtocolB(instanceOfO)    // "Protocol A"
print("\tClass P:")
printValueFromProtocolB(instanceOfP)    // "Protocol A"
print("\tClass Q:")
printValueFromProtocolB(instanceOfQ)    // "Protocol A"
print("\tClass R:")
printValueFromProtocolB(instanceOfR)    // "Protocol A"

// MARK: Calling via a function that requires the argument be the second Derived protocol
print("\n4) printValueFromProtocolC(_: PC):")
print("\tClass J:")
printValueFromProtocolC(instanceOfJ)    // "Protocol C"
print("\tClass K:")
printValueFromProtocolC(instanceOfK)    // "Protocol C"
print("\tClass L:")
printValueFromProtocolC(instanceOfL)    // "Protocol C"
print("\tClass P:")
printValueFromProtocolC(instanceOfP)    // "Protocol C"
print("\tClass Q:")
printValueFromProtocolC(instanceOfQ)    // "Class Q"
print("\tClass R:")
printValueFromProtocolC(instanceOfR)    // "Class R"

// MARK: Calling via a function that requires the argument be an instance or subclass of Class D
print("\n5) printValueFromClassD(_: D):")
print("\tClass D:")
printValueFromClassD(instanceOfD)       // "Class D"
print("\tClass E:")
printValueFromClassD(instanceOfE)       // "Class E"

// MARK: Calling via a function that requires the argument be an instance or subclass of Class A
print("\n6) printValueFromClassA(_: A):")
print("\tClass A:")
printValueFromClassA(instanceOfA)       // "Protocol A"
print("\tClass B:")
printValueFromClassA(instanceOfB)       // "Protocol A"
print("\tClass C:")
printValueFromClassA(instanceOfC)       // "Protocol A"
print("\tClass G:")
printValueFromClassA(instanceOfG)       // "Protocol A"

// MARK: Calling via a function that requires the argument be an instance or subclass of Class H
print("\n7) printValueFromClassH(_: H):")
print("\tClass H:")
printValueFromClassH(instanceOfH)       // "Protocol A"
print("\tClass I:")
printValueFromClassH(instanceOfI)       // "Protocol A"
print("\tClass N:")
printValueFromClassH(instanceOfN)       // "Protocol A"
print("\tClass O:")
printValueFromClassH(instanceOfO)       // "Protocol A"

// MARK: Calling via a function that requires the argument be an instance or subclass of Class J
print("\n8) printValueFromClassJ(_: J):")
print("\tClass J:")
printValueFromClassJ(instanceOfJ)       // "Protocol C"
print("\tClass K:")
printValueFromClassJ(instanceOfK)       // "Protocol C"
print("\tClass L:")
printValueFromClassJ(instanceOfL)       // "Protocol C"
print("\tClass P:")
printValueFromClassJ(instanceOfP)       // "Protocol C"

ПЕЧАТЬ:

1) Direct Method Call:
    Class A:
        Protocol A
    Class B:
        Class B
    Class C:
        Class C
    Class D:
        Class D
    Class E:
        Class E
    Class F:
        Class F
    Class G:
        Class G
    Class H:
        Protocol A
    Class I:
        Class I
    Class J:
        Protocol C
    Class K:
        Class K
    Class L:
        Class L
    Class M:
        Class M
    Class N:
        Class N
    Class O:
        Class O
    Class P:
        Class P
    Class Q:
        Class Q
    Class R:
        Class R

2) printValueFromProtocolA(_: PA):
    Class A:
        Protocol A
    Class B:
        Protocol A
    Class C:
        Protocol A
    Class D:
        Class D
    Class E:
        Class E
    Class F:
        Protocol A
    Class G:
        Protocol A
    Class H:
        Protocol A
    Class I:
        Protocol A
    Class J:
        Protocol C
    Class K:
        Protocol C
    Class L:
        Protocol C
    Class M:
        Protocol A
    Class N:
        Protocol A
    Class O:
        Protocol A
    Class P:
        Protocol C
    Class Q:
        Class Q
    Class R:
        Class R

3) printValueFromProtocolB(_: PB):
    Class D:
        Class D
    Class E:
        Class E
    Class H:
        Protocol A
    Class I:
        Protocol A
    Class J:
        Protocol C
    Class K:
        Protocol C
    Class L:
        Protocol C
    Class N:
        Protocol A
    Class O:
        Protocol A
    Class P:
        Protocol C
    Class Q:
        Class Q
    Class R:
        Class R

4) printValueFromProtocolC(_: PC):
    Class J:
        Protocol C
    Class K:
        Protocol C
    Class L:
        Protocol C
    Class P:
        Protocol C
    Class Q:
        Class Q
    Class R:
        Class R

5) printValueFromClassD(_: D):
    Class D:
        Class D
    Class E:
        Class E

6) printValueFromClassA(_: A):
    Class A:
        Protocol A
    Class B:
        Protocol A
    Class C:
        Protocol A
    Class G:
        Protocol A

7) printValueFromClassH(_: H):
    Class H:
        Protocol A
    Class I:
        Protocol A
    Class N:
        Protocol A
    Class O:
        Protocol A

8) printValueFromClassJ(_: J):
    Class J:
        Protocol C
    Class K:
        Protocol C
    Class L:
        Protocol C
    Class P:
        Protocol C

Ответы [ 2 ]

1 голос
/ 11 апреля 2020

Чтобы переопределить метод, этот метод должен быть реализован, поэтому, если вы попытаетесь использовать ключевое слово override в B, вы получите ошибку компиляции, поскольку, хотя A соответствует PA, он не реализует функцию в PA, но использует по умолчанию один вместо.

Так что это, скорее всего, причина путаницы, поскольку A не реализовал printValue, его нельзя переопределить, даже если с ним можно вызывать объекты типа A, a.printValue(), так как опирается на реализацию протокола по умолчанию.

Учитывая это, я думаю, что большинство тестовых случаев можно понять, почему они ведут себя так, как они, хотя я сосредоточился только на случае 6.

Реализация printValue() в А и посмотрим, что получится. Сначала вы получаете ошибки компиляции, которые должны быть исправлены, но после этого результат будет таким, как вы ожидали.

Относительно распечатки для случая 6, поскольку ничего не переопределяется из A, все объекты, отправленные на printValueFromClassA, будут будет рассматриваться как экземпляр A и будет использовать реализацию протокола по умолчанию.

0 голосов
/ 12 апреля 2020

OK. Я ухожу из проверок Джоакима, но теперь я могу говорить об этом более убедительно.

Это ошибка "sorta-kinda" в Swift.

По сути, если вы переопределите метод / свойство в Иерархия классов, основанная на протоколе с реализацией по умолчанию, vtable для иерархии классов не существует, пока вы не получите доступ к сущности на уровне, на котором она будет существовать без реализации по умолчанию.

Я знаю это не имеет смысла, и я боюсь, что мое объяснение может быть слишком длинным и уместным для этого места, но я опишу его подробно, здесь .

У меня есть Более простая (чем выше) суть, которая показывает это, здесь .

Я вставлю игровую площадку из этого суть, но она довольно длинная:

// This is the little demo, at the start.
protocol A {
    var thisIsRequired: String { get }
    var thisIsOptional: String { get }
}

extension A {
    var thisIsOptional: String { get { "" } set {} }
}

struct conformsToA: A {
    var thisIsRequired: String { "Struct conformsToA" }
}

// In the first protocol, we don't use a default implementation (required conformance).
protocol PA {
    var myName: String { get }
}

struct SA: PA {
    var myName: String = "Struct A"
}

class CA: PA {
    var myName: String = "Class A"
}

class CB: CA {
    override var myName: String { get { "Class B" } set { } }
}

// In the second protocol, we add a default implementation (optional conformance).
protocol PB: PA { }

extension PB {
    var myName: String { "Protocol B" }
}

// This will "fall back" to the default implementation.
struct SB: PB { }

struct SC: PB {
    var myName: String = "Struct C"
}

// This will "fall back" to the default implementation.
class CC: PB { }

class CD: CC {
    var myName: String = "Class D"
}

class CE: CD {
    override var myName: String { get { "Class E" } set { } }
}

class CF: PB {
    var myName: String = "Class F"
}

class CG: CF {
    override var myName: String { get { "Class G" } set { } }
}

let structInstanceSA = SA()
let classInstanceCA = CA()
let classInstanceCB = CB()

// Part one is all about the required conformance protocol.
print("PART ONE")
print("\tDirect Print:")
print("\t\tStruct A: \(structInstanceSA.myName)")
print("\t\tClass A: \(classInstanceCA.myName)")
print("\t\tClass B: \(classInstanceCB.myName)")

func printAsProtoclA(_ inItem: PA) -> String { inItem.myName }

print("\tprintAsProtoclA(_: PA):")
print("\t\tStruct A: \(printAsProtoclA(structInstanceSA))")
print("\t\tClass A: \(printAsProtoclA(classInstanceCA))")
print("\t\tClass B: \(printAsProtoclA(classInstanceCB))")

func printAsStructA(_ inItem: SA) -> String { inItem.myName }

print("\tprintAsStructA(_: SA):")
print("\t\tStruct A: \(printAsStructA(structInstanceSA))")

func printAsClassA(_ inItem: CA) -> String { inItem.myName }

print("\tprintAsClassA(_: SA):")
print("\t\tClass A: \(printAsClassA(classInstanceCA))")
print("\t\tClass B: \(printAsClassA(classInstanceCB))")

let structInstanceSB = SB()
let structInstanceSC = SC()
let classInstanceCC = CC()
let classInstanceCD = CD()
let classInstanceCE = CE()
let classInstanceCF = CF()
let classInstanceCG = CG()

// Part two is where things get interesting.
print("PART TWO")
print("\tDirect Print:")
print("\t\tStruct B: \(structInstanceSB.myName)")
print("\t\tStruct C: \(structInstanceSC.myName)")
print("\t\tClass C: \(classInstanceCC.myName)")
print("\t\tClass D: \(classInstanceCD.myName)")
print("\t\tClass E: \(classInstanceCE.myName)")
print("\t\tClass F: \(classInstanceCF.myName)")
print("\t\tClass G: \(classInstanceCG.myName)")

func printAsProtoclB(_ inItem: PB) -> String { inItem.myName }

print("\tprintAsProtoclA(_: PA):")
print("\t\tStruct B: \(printAsProtoclA(structInstanceSB))")
print("\t\tStruct C: \(printAsProtoclA(structInstanceSC))")
print("\t\tClass C: \(printAsProtoclA(classInstanceCC))")
print("\t\tClass D: \(printAsProtoclA(classInstanceCD))")
print("\t\tClass E: \(printAsProtoclA(classInstanceCE))")
print("\t\tClass F: \(printAsProtoclA(classInstanceCF))")
print("\t\tClass G: \(printAsProtoclA(classInstanceCG))")

print("\tprintAsProtoclB(_: PB):")
print("\t\tStruct B: \(printAsProtoclA(structInstanceSB))")
print("\t\tStruct C: \(printAsProtoclA(structInstanceSC))")
print("\t\tClass C: \(printAsProtoclA(classInstanceCC))")
print("\t\tClass D: \(printAsProtoclA(classInstanceCD))")
print("\t\tClass E: \(printAsProtoclA(classInstanceCE))")
print("\t\tClass F: \(printAsProtoclA(classInstanceCF))")
print("\t\tClass G: \(printAsProtoclA(classInstanceCG))")

func printAsStructB(_ inItem: SB) -> String { inItem.myName }

print("\tprintAsStructB(_: SB):")
print("\t\tStruct B: \(printAsStructB(structInstanceSB))")

func printAsStructC(_ inItem: SC) -> String { inItem.myName }

print("\tprintAsStructC(_: SC):")
print("\t\tStruct C: \(printAsStructC(structInstanceSC))")

func printAsClassC(_ inItem: CC) -> String { inItem.myName }

print("\tprintAsClassC(_: CC):")
print("\t\tClass C: \(printAsClassC(classInstanceCC))")
print("\t\tClass D: \(printAsClassC(classInstanceCD))")
print("\t\tClass E: \(printAsClassC(classInstanceCE))")

func printAsClassD(_ inItem: CD) -> String { inItem.myName }

print("\tprintAsClassD(_: CD):")
print("\t\tClass D: \(printAsClassD(classInstanceCD))")
print("\t\tClass E: \(printAsClassD(classInstanceCE))")

func printAsClassF(_ inItem: CF) -> String { inItem.myName }

print("\tprintAsClassF(_: SF):")
print("\t\tClass F: \(printAsClassF(classInstanceCF))")
print("\t\tClass G: \(printAsClassF(classInstanceCG))")

// In a perfect world, this would be a syntax error:
let name = classInstanceCC.myName

И вот UML для вышеуказанного: UML for the above playground

...