Функция протокола возвращая собственную личность


у меня есть протокол, который возвращает копию объекта:

protocol P {
    func copy() -> Self
}

и класс C, который реализует P:

class C : P {
    func copy() -> Self *or* C {
        return C()
    }
}

однако, независимо от того, ставлю ли я возвращаемое значение как C или как Self, ничего не работает, кроме случая, когда я префикс class C С final. Но если я хочу подкласс C, то ничего не будет работать. Есть ли способ обойти это?

8 57

8 ответов:

проблема в том, что вы даете обещание, которое компилятор не может доказать, что вы будете держать.

Итак, вы создали это обещание: вызов copy() вернет свой собственный тип, полностью инициализированный.

но тогда вы реализовали copy() таким образом:

func copy() -> Self {
    return C()
}

теперь я подкласс, который не переопределяет copy(). И я возвращаю a C, не полностью инициализирован Self (что я и обещал). Так что это нехорошо. Как насчет:

func copy() -> Self {
    return Self()
}

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

хорошо, а как насчет:

func copy() -> C {
    return C()
}

да, но это не вернет Self. Он возвращает C. Ты все еще не сдержал своего обещания.

но ObjC может это сделать!- Ну, вроде того. В основном потому, что ему все равно, сдержишь ли ты свое обещание так, как это делает Свифт. Если вам не удастся реализовать copyWithZone: в подклассе, вы можете не полностью инициализировать свой объект. Компилятор даже не предупредит вас, что вы это сделали.

" но почти все в ObjC может быть переведено на Swift, и ObjC имеет NSCopying."Да, это так, и вот как это определяется:

func copy() -> AnyObject!

так что вы можете сделать то же самое (нет причин ! здесь):

protocol Copyable {
  func copy() -> AnyObject
}

это говорит: "Я ничего не обещаю о том, что вы получите обратно.- Можно также сказать:

protocol Copyable {
  func copy() -> Copyable
}

вот обещание, которое ты можешь дать.

но мы можем немного подумать о C++ и вспомнить, что есть обещание, которое мы можете make. Мы можем обещать, что мы и все наши подклассы реализуем определенные виды инициализаторов, и Swift будет обеспечивать это (и поэтому может доказать, что мы говорим правду):

protocol Copyable {
  init(copy: Self)
}

class C : Copyable {
  required init(copy: C) {
    // Perform your copying here.
  }
}

и именно так вы должны выполнять копии.

мы можем сделать этот шаг дальше, но он использует dynamicType и я не проверял его экстенсивно, чтобы убедиться, что это всегда то, что мы хотим, но это должно быть правильно:

protocol Copyable {
  func copy() -> Self
  init(copy: Self)
}

class C : Copyable {
  func copy() -> Self {
    return self.dynamicType(copy: self)
  }

  required init(copy: C) {
    // Perform your copying here.
  }
}

здесь мы обещаем, что есть инициализатор, который выполняет копии для нас, а затем мы можем во время выполнения определить, какой из них вызывать, давая нам синтаксис метода, который вы искали.

С Swift 2, мы можем использовать расширения протокола для этого.

protocol Copyable {
    init(copy:Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(copy: self)
    }
}

на самом деле, есть трюк, который позволяет легко возвращение Self при необходимости по протоколу (суть):

/// Cast the argument to the infered function return type.
func autocast<T>(some: Any) -> T? {
    return some as? T
}

protocol Foo {
    static func foo() -> Self
}

class Vehicle: Foo {
    class func foo() -> Self {
        return autocast(Vehicle())!
    }
}

class Tractor: Vehicle {
    override class func foo() -> Self {
        return autocast(Tractor())!
    }
}

func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

let vehicle = Vehicle.foo()
let tractor = Tractor.foo()

print(typeName(vehicle)) // Vehicle
print(typeName(tractor)) // Tractor

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

public protocol Creatable {

    associatedtype ObjectType = Self

    static func create() -> ObjectType
}

class MyClass {

    // Your class stuff here
}

extension MyClass: Creatable {

    // Define the protocol function to return class type
    static func create() -> MyClass {

         // Create an instance of your class however you want
        return MyClass()
    }
}

let obj = MyClass.create()

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

protocol Copyable, NSCopying {
    typealias Prototype
    init(copy: Prototype)
    init(deepCopy: Prototype)
}
class C : Copyable {
    typealias Prototype = C // <-- requires adding this line to classes
    required init(copy: Prototype) {
        // Perform your copying here.
    }
    required init(deepCopy: Prototype) {
        // Perform your deep copying here.
    }
    @objc func copyWithZone(zone: NSZone) -> AnyObject {
        return Prototype(copy: self)
    }
}

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

как указано выше, проблема заключается в неоднозначности возвращаемого типа для функции copy (). Это можно очень четко проиллюстрировать, разделив функции copy () - > C и copy () - > P:

Итак, предполагая, что вы определяете протокол и класс как следует:

protocol P
{
   func copy() -> P
}

class C:P  
{        
   func doCopy() -> C { return C() }       
   func copy() -> C   { return doCopy() }
   func copy() -> P   { return doCopy() }       
}

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

например:

var aC:C = C()   // aC is of type C
var aP:P = aC    // aP is of type P (contains an instance of C)

var bC:C         // this to test assignment to a C type variable
var bP:P         //     "       "         "      P     "    "

bC = aC.copy()         // OK copy()->C is used

bP = aC.copy()         // Ambiguous. 
                       // compiler could use either functions
bP = (aC as P).copy()  // but this resolves the ambiguity.

bC = aP.copy()         // Fails, obvious type incompatibility
bP = aP.copy()         // OK copy()->P is used

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

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

конечный результат больше похож:

protocol P
{
   func copyAsP() -> P
}

class C:P  
{
   func copy() -> C 
   { 
      // there usually is a lot more code around here... 
      return C() 
   }
   func copyAsP() -> P { return copy() }       
}

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

просто бросает свою шляпу на ринг здесь. Нам нужен был протокол, который возвращал необязательный тип, к которому применялся протокол. Мы также хотели, чтобы переопределение явно возвращало тип, а не только Self.

трюк заключается в том, что вместо использования "Self" в качестве возвращаемого типа вы вместо этого определяете связанный тип, который вы устанавливаете равным Self, а затем используете этот связанный тип.

вот старый способ, используя себя...

protocol Mappable{
    static func map() -> Self?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> Self? {
        ...
    }
}

вот новый способ использования связанный тип. Обратите внимание, что возвращаемый тип теперь явный, а не 'Self'.

protocol Mappable{
    associatedtype ExplicitSelf = Self
    static func map() -> ExplicitSelf?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> SomeSpecificClass? {
        ...
    }
}

добавить к ответам с associatedtype таким образом, я предлагаю переместить создание экземпляра к реализации по умолчанию расширения протокола. Таким образом, соответствующие классы не должны будут реализовывать его, тем самым избавляя нас от дублирования кода:

protocol Initializable {
    init()
}

protocol Creatable: Initializable {
    associatedtype Object: Initializable = Self
    static func newInstance() -> Object
}

extension Creatable {
    static func newInstance() -> Object {
        return Object()
    }
}

class MyClass: Creatable {
    required init() {}
}

class MyOtherClass: Creatable {
    required init() {}
}

// Any class (struct, etc.) conforming to Creatable
// can create new instances without having to implement newInstance() 
let instance1 = MyClass.newInstance()
let instance2 = MyOtherClass.newInstance()