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

Я, расширение протокола и не конечный класс

Я попытался написать статический метод для UIView, который запускает представление этого класса из nib. Метод должен быть общим и работать над каждым подклассом UIView. Также я хочу сохранить информацию о типе - так, например, в этом коде

let myView = MyView.loadFromNib()

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

Похоже, это должно работать:

protocol NibLoadable {
    static func loadFromNib(name: String?) -> Self
}

extension NibLoadable where Self: UIView {
    static func loadFromNib(name: String? = nil) -> Self {
        let nibName = name ?? "\(self)"
        let nib = UINib(nibName: nibName, bundle: nil)
        return nib.instantiateWithOwner(nil, options: nil)[0] as! Self
    }
}

extension UIView: NibLoadable {}

Но это не так. Я получаю ошибку компиляции

Method 'loadFromNib' in non-final class 'UIView' must return `Self` to conform to protocol 'NibLoadable'

И происходят две странные вещи. Во-первых, если я изменю объявление протокола на

protocol NibLoadable {
    static func loadFromNib(name: String?) -> UIView
}

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

extension NibLoadable {
    ...
}

И он продолжает работать!

Так может ли кто-нибудь объяснить мне, почему мой первый вариант не удается, почему вторая и третья работают нормально и как это связано с окончательными классами?

4b9b3361

Ответ 1

Вот мое понимание того, что вы видите:

  • Вы получаете ошибку компиляции Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to conform to protocol 'NibLoadable' в точке, где вы объявляете extension UIView: NibLoadable {}. Давайте посмотрим, что это означает для компилятора. Он говорит: "UIView (и все его подклассы, поскольку это не конечный класс), принимают протокол NibLoadable. Это означает, что для UIView существует метод с сигнатурой static func loadFromNib(name: String?) -> UIView, потому что Self в этом контексте UIView."

    Но что это значит для подклассов UIView? Они наследуют их соответствие и могут наследовать реализацию метода из самого UIView. Таким образом, любой подкласс UIView может иметь метод с сигнатурой static func loadFromNib(name: String? = nil) -> UIView. Однако протокол NibLoadable, который также соответствует всем подклассам, указывает, что возвращаемый тип этого метода должен быть Self. А в случае любого подкласса UIView (например, пусть говорят "MyView" ), возвращаемый тип унаследованного метода будет UIView, а не MyView. Поэтому любой подкласс будет нарушать контракт протокола. Я понимаю, что расширение вашего протокола использует Self и не создало бы эту проблему, но технически вы все равно могли бы реализовать метод непосредственно в расширении UIView, и похоже, что компилятор Swift просто не позволит его вообще эта причина. Более эффективная реализация может найти компилятор Swift, подтверждающий существование расширений протокола, который обеспечивает реализацию, и не существует противоречивой унаследованной реализации, но в настоящее время это просто не существует. Поэтому для безопасности я предполагаю, что компилятор запрещает ЛЮБЫЕ протоколы, которые имеют методы с Self типами возврата, которые принимаются нечетным классом. Таким образом, вы видите ошибку.

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

  • Причина, по которой изменение типа возвращаемого значения в протоколе для UIView устраняет все, заключается в том, что не имеет "Я", поскольку возвращаемый тип теперь уменьшает озабоченность компилятора о наследуемых версиях метода, имеющего несоответствующий тип возврата. Например, если UIView должен был реализовать метод static func loadFromNib(name: String?) -> UIView, а подклассы, унаследованные этим методом, контракт по контракту будет сохраняться для этих подклассов, поэтому нет проблем!

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

  • Удаление предложения where работает только в этом конкретном случае, потому что вы изменили метод протокола для возврата UIView, а расширение протокола определяет реализацию метода соответствия, которая возвращает Self, а затем только UIView получает это расширение в ваш образец кода. Таким образом, требование протокола метода, возвращающего UIView, соответствует реализации UIView, которая возвращает Self (которая в данном случае оказывается UIView). Но, если вы попытаетесь сделать любой тип, отличный от UIView, получить способ расширения протокола, например.

    class SomeClass : NibLoadable {}
    

    или даже

    class MyView:UIView, NibLoadable {}
    

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

Подводя итог некоторым ключевым моментам:

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

    protocol NibLoadable {
        static func loadFromNib(name: String?) -> Self
    }
    
    extension UIView: NibLoadable {}
    

    Итак, похоже, что компилятор не позволяет нефинальным классам принимать протоколы, используя стандартные реализации методов, которые имеют тип возврата Self, period.

  • Если вы меняете сигнатуру метода протокола, возвращаете UIView вместо Self, что предупреждение конкретного компилятора исчезает, потому что больше нет возможности подклассов, наследующих тип возврата суперкласса и нарушения протокола. Затем вы можете добавить соответствие протоколу UIView с расширением вашего протокола. Однако вы получите другую ошибку, если попытаетесь принять протокол для любого типа, кроме UIView, потому что тип возвращаемого протокола UIView не будет соответствовать типу возврата метода расширения Self, за исключением одного случая UIView. Это может быть ошибкой, на мой взгляд, потому что Self для любого подкласса UIView должен соответствовать требуемому контракту типа UIView.

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

Я уверен, что во всем этом есть несколько ошибок, но кто-то из команды Swift должен подтвердить это, чтобы быть уверенным.

ОБНОВЛЕНИЕ

Некоторые пояснения от команды Swift в этой теме Twitter:

https://twitter.com/_danielhall/status/737782965116141568

Как и предполагалось, это ограничение компилятора (хотя, по-видимому, не считается прямой ошибкой), что согласование протокола не рассматривает подтипы, только точные совпадения типа. Поэтому extension UIView:NibLoadable {} будет работать, когда метод протокола определяет тип возврата UIView, но extension MyView:NibLoadable {} не будет.

Ответ 2

Использовать следующий код должен быть в порядке (в Swift 3):

protocol Nibable {}
extension Nibable {
    static func loadFromNib() -> Self? {
        return Bundle.main.loadNibNamed(String(describing: 
type(of:Self.self)), owner: nil, options: nil)?.first as? Self
    }
}

final class ViewFromNib: UIView {}
extension ViewFromNib: Nibable {}

var nibView = ViewFromNib.loadFromNib()