Подтвердить что ты не робот

Протокол func возвращает Self

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

protocol P {
    func copy() -> Self
}

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

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

Однако, если я верну свое значение как Self я получаю следующую ошибку:

Невозможно преобразовать возвращаемое выражение типа 'C', чтобы вернуть тип 'Self'

Я также попробовал вернуть C

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

Это привело к следующей ошибке:

Метод 'copy()' в непределом классе 'C' должен возвращать Self для соответствия протоколу 'P'

Ничего не работает, кроме случая, когда я префикс class C final то есть:

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

Однако, если я хочу подкласс C, тогда ничего не получится. Есть ли способ обойти это?

4b9b3361

Ответ 1

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

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

Но тогда вы внедрили copy() следующим образом:

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

Теперь я подкласс, который не переопределяет copy(). И я возвращаю 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
}

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

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

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.
  }
}

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

Ответ 2

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

protocol Copyable {
    init(copy:Self)
}

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

Ответ 3

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

/// 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

Ответ 4

Существует еще один способ сделать то, что вы хотите, что предполагает использование связанного с 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()

Ответ 5

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

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)
    }
}

Ответ 6

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

Как указано выше, проблема заключается в двусмысленности возвращаемого типа для функции 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

В заключение, это будет работать в ситуациях, когда вы либо не используете функцию base class 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() }       
}

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

Ответ 7

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

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

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

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

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

Здесь новый способ использования связанного типа. Обратите внимание, что тип возвращаемого значения теперь явно, а не "Я" .

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

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

Ответ 8

Чтобы добавить ответы с помощью метода 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()