it-roy-ru.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"
43
VojtaStavik

Короткий ответ: расширения протокола не вызывают полиморфизм классов. Это имеет определенный смысл, потому что протокол может быть принят структурой или перечислением, и потому что мы не хотим, чтобы простое принятие протокола вводило динамическую диспетчеризацию там, где это не нужно.

Таким образом, в getColor() переменная экземпляра color (которая может быть более точно записана как self.color) не означает, что вы думаете, что она делает, потому что вы мыслите класс-полиморфно, а протокол - нет. Так что это работает:

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

... потому что вы просите class разрешить color, но это не то, что вы ожидаете:

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

... потому что метод getColor полностью определен в расширении протокола. Вы можете решить проблему, переопределив getColor в B:

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

Теперь вызывается класс getColor, и он имеет полиморфное представление о том, что такое self.

42
matt

Мне удалось заставить его работать, определив color для Color и переключив список реализации для B. Не очень хорошо, если B должен быть A.

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

Здесь есть две очень разные проблемы: динамическое поведение протоколов и разрешение реализаций протокола «по умолчанию».

  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. Теперь, в ваш ответ , вы задаетесь вопросом, почему это немного распадается, когда B является подклассом A

    Я думаю, это помогает помнить, что реализации метода в расширениях протокола являются реализациями по умолчанию, то есть реализациями, которые должны использоваться, если соответствующий класс сам не реализует их. Источником путаницы в вашем случае является тот факт, что B соответствует RedColor, который имеет реализацию по умолчанию для color, но B также является подклассом A, который соответствует Color, который имеет другую реализацию по умолчанию color.

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

Я знаю, что это старый вопрос, так что вы, вероятно, давно перешли к другим вещам, и это нормально. Но если вы все еще пытаетесь найти правильный способ рефакторинга этого кода, расскажите немного о том, что на самом деле представляет эта иерархия классов и что на самом деле представляет это наследование протокола, и мы могли бы предложить более конкретные рекомендации. Это один из тех случаев, когда абстрактные примеры только еще больше запутывают проблему. Давайте посмотрим, что на самом деле типы/протоколы. (Если у вас есть рабочий код, http://codereview.stackexchange.com может быть лучшим местом.)

4
Rob

Я столкнулся с этой проблемой, пытаясь реализовать «необязательный» метод через протокол. Его можно заставить работать в структурах, в классах, которые не наследуются, а также в классах, которые наследуют от базы, которая реализует метод, отличный от протокола по умолчанию, который можно переопределить. Единственный случай, который не работает, - это класс, который наследует от базы, которая объявляет соответствие, но не предоставляет свою собственную реализацию «не по умолчанию» - в этом случае расширение протокола по умолчанию «встроено» в базовый класс и не может быть переопределено или переопределено. 

Простой пример:

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