В Swift, как мне вернуть объект того же типа, который соответствует протоколу

У меня есть следующий протокол

protocol JSONInitializable {
    static func initialize(fromJSON: NSDictionary) throws -> Self?
}

Теперь я пытаюсь заставить эту функцию возвращать любой класс, соответствующий протоколу. т.е. если у меня есть класс Foo, соответствующий протоколу, я хочу вернуть объект Foo из метода. Как я могу это сделать?

extension Foo: JSONInitializable {
    static func initialize(fromJSON: NSDictionary) throws -> Foo? {
    }
}

Я получаю сообщение об ошибке:

Метод «инициализация» в нефинальном классе «Foo» должен возвращать «Self», чтобы соответствовать протоколу «JSONInitializable»


person smac89    schedule 28.03.2016    source источник


Ответы (2)


Автозаполнение вам поможет, но в основном, если Foo это класс, вам просто нужен метод, который точно соответствует сигнатуре метода в вашем протоколе:

class Foo {}

protocol JSONInitializable {
    static func initialize(fromJSON: [String : AnyObject]) throws -> Self?
}

extension Foo: JSONInitializable {
    static func initialize(fromJSON: [String : AnyObject]) throws -> Self? {
        return nil
    }
}

В качестве примечания здесь вам нужно будет заменить в этом методе все экземпляры Foo на Self. Если вы хотите использовать конструктор, он должен быть помечен как требуемый.

Имея это в виду, вероятно, имеет смысл изменить ваш протокол на требование инициализатора, а не на статический метод с именем initialize, и в этом случае взгляните на Ответ Блейка Локли. Однако этот ответ отвечает на вопрос о том, как поступать с Self в протоколах, поскольку, безусловно, бывают случаи, когда нам может понадобиться метод, возвращающий Self, который не является инициализатором.


Если Foo не является классом, это тип значения, который не может быть подклассом, и поэтому мы возвращаем имя типа:

struct Foo: JSONInitializable {
    static func initialize(fromJSON: [String : AnyObject]) throws -> Foo? {
        return nil
    }
}
enum Foo: JSONInitializable {
    case One, Two, Three

    static func initialize(fromJSON: [String : AnyObject]) throws -> Foo? {
        return nil
    }
}

Причина, по которой вам нужно вернуть Self? из метода в случае класса, заключается в наследовании. Протокол заявляет, что если вы будете ему соответствовать, у вас будет метод с именем initialize, тип возвращаемого значения которого будет опциональной версией того, чем вы являетесь.

Если мы напишем метод initialize для Foo так, как вы его написали, а затем, если мы создадим подкласс Foo с Bar, то теперь мы нарушим наше соответствие протоколу. Bar унаследовал соответствие протоколу от Foo, но у него нет метода, который вызывается initialize и возвращает Bar?. У него есть тот, который возвращает Foo.

Использование здесь Self означает, что когда наши подклассы наследуют этот метод, он будет возвращать любой тип, которым они являются. Self является эквивалентом Swift Objective-C instancetype.

person nhgrif    schedule 28.03.2016
comment
Так что это позволит мне вернуть объект типа Foo из метода? - person smac89; 29.03.2016
comment
Он возвращает Foo, если вы вызываете его для экземпляра Foo. Если вы создадите подкласс Foo, чтобы сделать class Bar: Foo, он вернет Bar. - person nhgrif; 29.03.2016
comment
Я только что попробовал это, и теперь это дает мне ошибку, когда я пытаюсь вернуть объект типа Foo из метода. Он говорит Cannot convert expression of type 'Foo' to return type 'Self?' Я использую xcode 7.3, если это что-то помогает - person smac89; 29.03.2016
comment
В этом методе вам придется заменить все ссылки Foo на Self. Если вы хотите использовать конструктор, он должен быть помечен как required. - person nhgrif; 29.03.2016
comment
Хм, я немного потерял, как это сделать. Я хочу сделать let f = Foo(); return f. Это дает ошибку. Затем я пробую return f.self() еще одну ошибку. Так что я не уверен, что вернуть сейчас - person smac89; 29.03.2016
comment
let f = self.init(), но Foo должен иметь метод init без аргументов, помеченный как required, чтобы это работало. - person nhgrif; 29.03.2016
comment
Ага, я понимаю, зачем нужен обязательный конструктор. Спасибо - person smac89; 29.03.2016
comment
И необходимые инициализаторы должны быть объявлены в самом классе (а не в расширениях). - person nhgrif; 29.03.2016

Как упоминалось в ответе nhgrif, измените тип возвращаемого значения Foo? на self?.

В качестве альтернативы

В Swift вы можете объявить inits в протоколе, поэтому попробуйте что-то вроде этого:

protocol JSONInitializable {
    init?(fromJSON: NSDictionary) throws
}

extension Foo: JSONInitializable {
    required init?(fromJSON: NSDictionary) throws {
      //Possibly throws or returns nil
    }
}
person Blake Lockley    schedule 28.03.2016
comment
Я думаю, вам нужны convenience и required в этом расширении - person smac89; 29.03.2016
comment
Вам нужно required. Вам не обязательно нужен convenience. Также стоит упомянуть, что протоколы Objective-C могут и имеют объявленные инициализаторы как часть протокола. Дополнительные сведения см. в протоколе NSCoding. - person nhgrif; 29.03.2016
comment
Я только что попробовал этот способ, и ошибка говорит о том, что я могу сделать это только в определении нефинального класса, т.е. расширения не работают - person smac89; 29.03.2016
comment
@ Smac89 Smac89 вы уже объявили свой класс Foo до расширения? если нет, вы можете включить протокол в свою реализацию без использования расширения, например: Foo: JSONInitializable { required init?...} - person Blake Lockley; 29.03.2016
comment
Да, класс уже объявлен, но я не могу его изменить, поэтому я искал создание этого дополнительного конструктора. - person smac89; 29.03.2016