У меня есть протокол 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
Ничего не работает , за исключением случая , когда я PREfix class C
с , final
т.е. сделать:
final class C : P {
func copy() -> C {
return C()
}
}
Однако, если я хочу создать подкласс C, ничего не получится. Есть ли способ обойти это?
swift
protocols
subclassing
swift-protocols
Aeubanks
источник
источник
class
не являетсяfinal class
[[[self class] alloc] init]
. Итак, я предполагаю, что вопрос в том, существует ли безопасный для типов способ вызвать текущий класс и вызвать метод инициализации?Ответы:
Проблема в том, что вы даете обещание, которое компилятор не может доказать, что вы его сдержите.
Итак, вы создали это обещание: Вызов
copy()
вернет свой собственный тип, полностью инициализированный.Но тогда вы реализовали
copy()
так:func copy() -> Self { return C() }
Теперь я подкласс, который не отменяет
copy()
. И я возвращаюC
не полностью инициализированныйSelf
(что обещал). Так что это нехорошо. Как насчет:func copy() -> Self { return Self() }
Что ж, это не скомпилируется, но даже если бы это было так, это было бы бесполезно. У подкласса может не быть тривиального конструктора, поэтому
D()
может быть даже незаконным. (Хотя см. Ниже.)Хорошо, а как насчет:
func copy() -> C { return C() }
Да, но это не возвращается
Self
. Он возвращаетсяC
. Ты все еще не сдерживаешь своего обещания."Но ObjC может это сделать!" Ну вроде как. В основном потому, что его не волнует, сдержите ли вы свое обещание, как это делает Swift. Если вам не удастся реализовать
copyWithZone:
в подклассе, вы можете не полностью инициализировать свой объект. Компилятор даже не предупредит вас, что вы это сделали.«Но почти все в ObjC можно перевести на Swift, а в ObjC есть
NSCopying
». Да, это так, и вот как это определяется:func copy() -> AnyObject!
Таким образом, вы можете сделать то же самое (здесь нет причин!):
protocol Copyable { func copy() -> AnyObject }
Это говорит: «Я ничего не обещаю о том, что вы получите в ответ». Вы также можете сказать:
protocol Copyable { func copy() -> Copyable }
Это обещание, которое вы можете дать.
Но мы можем немного подумать о C ++ и вспомнить, что есть обещание, которое мы можем дать. Мы можем пообещать, что мы и все наши подклассы будем реализовывать определенные типы инициализаторов, и 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. } }
Здесь мы обещаем, что есть инициализатор, который выполняет копии для нас, а затем мы можем во время выполнения определить, какой из них вызвать, предоставив нам синтаксис метода, который вы искали.
источник
func copy() -> C
работало в предыдущих бета-версиях, и это было согласованно, потому что соответствие протоколу не передавалось по наследству. (Теперь кажется, что соответствие протокола передается по наследству иfunc copy() -> C
не работает.)init(copy: C)
вместо этогоinit(copy: Self)
:(Self
но все инициализаторы должны принять статически типизированную переменную, тоC
есть, это не так уж много улучшений для простого возвратаAnyObject
в первую очередь.self.dynamicType.init( ... )
В Swift 2 мы можем использовать для этого расширения протокола.
protocol Copyable { init(copy:Self) } extension Copyable { func copy() -> Self { return Self.init(copy: self) } }
источник
return Self(copy: self)
(по крайней мере, в Swift 2.2).Есть еще один способ сделать то, что вы хотите, - воспользоваться преимуществами ассоциированного типа 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()
источник
На самом деле есть трюк, который позволяет легко вернуться,
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
источник
return Vehicle() as! Self
foo()
не выполняется принудительно, каждыйVehicle
потомок безfoo()
специальной реализации вызовет очевидный сбойautocast()
. Например:class SuperCar: Vehicle { } let superCar = SuperCar.foo()
. ЭкземплярVehicle
не может быть понижен доSuperCar
- поэтому принудительное развертывание nil в 'autocast ()' приводит к сбою.foo()
. Единственное требование - классFoo
должен иметь требуемый инициализатор, чтобы это работало, как показано ниже.class Vehicle: Foo { public required init() { // Some init code here } class func foo() -> Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -> Self { return autocast(Tractor())! }*/ }
Следуя предложению Роба, это можно сделать более универсальным для связанных типов . Я немного изменил пример, чтобы продемонстрировать преимущества подхода.
protocol Copyable: NSCopying { associatedtype 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() } }
Конечно, мой контекст и функции совершенно разные, но, исходя из сути вопроса, я постарался максимально приблизиться к приведенному примеру.
источник
Swift 5.1 теперь позволяет принудительное применение к себе,
as! Self
1> protocol P { 2. func id() -> Self 3. } 9> class D : P { 10. func id() -> Self { 11. return D() 12. } 13. } error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self' return D() ^~~ as! Self 9> class D : P { 10. func id() -> Self { 11. return D() as! Self 12. } 13. } //works
источник
Просто бросаю здесь свою шляпу на ринг. Нам был нужен протокол, который возвращал бы необязательный тип, к которому был применен протокол. Мы также хотели, чтобы переопределение явно возвращало тип, а не только Self.
Уловка заключается в том, что вместо того, чтобы использовать «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()
источник