Функция протокола возвращая собственную личность
у меня есть протокол, который возвращает копию объекта:
protocol P {
func copy() -> Self
}
и класс C, который реализует P:
class C : P {
func copy() -> Self *or* C {
return C()
}
}
однако, независимо от того, ставлю ли я возвращаемое значение как C или как Self, ничего не работает, кроме случая, когда я префикс class C С final. Но если я хочу подкласс C, то ничего не будет работать. Есть ли способ обойти это?
8 ответов:
проблема в том, что вы даете обещание, которое компилятор не может доказать, что вы будете держать.
Итак, вы создали это обещание: вызов
copy()вернет свой собственный тип, полностью инициализированный.но тогда вы реализовали
copy()таким образом:func copy() -> Self { return C() }теперь я подкласс, который не переопределяет
copy(). И я возвращаю aC, не полностью инициализирован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()