Я стараюсь помещать только необходимые (сохраненные свойства, инициализаторы) в определения моих классов и перемещать все остальное в свои собственные extension
, вроде как отдельный extension
логический блок, с которым я бы тоже сгруппировал // MARK:
.
Например, для подкласса UIView я бы получил расширение для вещей, связанных с макетом, одно для подписки и обработки событий и так далее. В этих расширениях мне неизбежно придется переопределить некоторые методы UIKit, например layoutSubviews
. Я никогда не замечал никаких проблем с этим подходом - до сегодняшнего дня.
Возьмем, к примеру, эту иерархию классов:
public class C: NSObject {
public func method() { print("C") }
}
public class B: C {
}
extension B {
override public func method() { print("B") }
}
public class A: B {
}
extension A {
override public func method() { print("A") }
}
(A() as A).method()
(A() as B).method()
(A() as C).method()
Выход есть A B C
. Для меня это не имеет смысла. Я читал о статической отправке расширений протокола, но это не протокол. Это обычный класс, и я ожидаю, что вызовы методов будут динамически отправляться во время выполнения. Очевидно, что вызов C
должен по крайней мере динамически отправляться и производиться C
?
Если я удаляю наследование NSObject
и создаю C
корневой класс, компилятор жалуется, говоря declarations in extensions cannot override yet
, о чем я уже читал. Но как наличие NSObject
корневого класса меняет ситуацию?
Перемещение обоих переопределений в их объявление класса дает A A A
ожидаемые результаты, перемещение только B
производит A B B
, перемещение только A
производит C B C
, последнее из которых не имеет для меня абсолютно никакого смысла: даже тот, который статически типизирован для A
создания A
-output, больше не имеет!
Добавление dynamic
ключевого слова к определению или переопределению, кажется, дает мне желаемое поведение «с этой точки в иерархии классов вниз» ...
Давайте изменим наш пример на что-то менее сконструированное, что на самом деле заставило меня опубликовать этот вопрос:
public class B: UIView {
}
extension B {
override public func layoutSubviews() { print("B") }
}
public class A: B {
}
extension A {
override public func layoutSubviews() { print("A") }
}
(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()
Теперь получаем A B A
. Здесь я никак не могу сделать макет UIView динамическим.
Перемещение как переопределение в их объявлении класса возвращает нас A A A
снова, только либо только B по - прежнему получает нас A B A
. dynamic
снова решает мои проблемы.
Теоретически я мог бы добавить dynamic
ко всему, override
что делаю, но чувствую, что делаю здесь что-то еще не так.
Неужели неправильно использовать extension
s для группировки кода, как я?
источник
Ответы:
Расширения не могут / не должны отменять.
Невозможно переопределить функциональность (например, свойства или методы) в расширениях, как описано в Руководстве Apple по Swift.
Руководство разработчика Swift
Компилятор позволяет вам переопределить расширение для совместимости с Objective-C. Но на самом деле это нарушает языковую директиву.
«Это напомнило мне« Три закона робототехники » Айзека Азимова »
Расширения ( синтаксический сахар ) определяют независимые методы, которые получают свои собственные аргументы. Функция, которая вызывается, т.е.
layoutSubviews
зависит от контекста, о котором компилятор знает, когда код компилируется. UIView наследуется от UIResponder, который наследуется от NSObject, поэтому переопределение в расширении разрешено, но не должно быть .Таким образом, в группировке нет ничего плохого, но вы должны переопределить класс, а не расширение.
Примечания к директиве
Вы можете использовать только
override
метод суперкласса, то естьload()
initialize()
в расширении подкласса, если метод совместим с Objective-C.Поэтому мы можем посмотреть, почему он позволяет вам компилировать с использованием
layoutSubviews
.Все приложения Swift выполняются внутри среды выполнения Objective-C, за исключением случаев, когда используются чистые фреймворки только для Swift, которые допускают среду выполнения только для Swift.
Как мы выяснили, среда выполнения Objective-C обычно вызывает два основных метода класса
load()
иinitialize()
автоматически при инициализации классов в процессах вашего приложения.По поводу
dynamic
модификатораИз библиотеки разработчиков Apple (archive.org)
Вы можете использовать
dynamic
модификатор, чтобы требовать, чтобы доступ к членам динамически отправлялся через среду выполнения Objective-C.Когда API-интерфейсы Swift импортируются средой выполнения Objective-C, нет никаких гарантий динамической отправки для свойств, методов, индексов или инициализаторов. Компилятор Swift может по-прежнему девиртуализировать или встроить доступ к членам для оптимизации производительности вашего кода, минуя среду выполнения Objective-C. 😳
Таким образом, это
dynamic
может быть применено к вашемуlayoutSubviews
->,UIView Class
поскольку оно представлено Objective-C, и доступ к этому члену всегда используется с использованием среды выполнения Objective-C.Вот почему компилятор, позволяющий использовать
override
иdynamic
.источник
Одна из целей Swift - статическая диспетчеризация, или, скорее, сокращение динамической диспетчеризации. Однако Obj-C - очень динамичный язык. Ситуация, которую вы видите, основана на связи между двумя языками и тем, как они работают вместе. На самом деле он не должен компилироваться.
Один из основных моментов в расширениях заключается в том, что они предназначены для расширения, а не для замены / переопределения. Как из названия, так и из документации ясно, что это намерение. Действительно, если вы удалите ссылку на Obj-C из своего кода (удалите
NSObject
как суперкласс), он не будет компилироваться.Итак, компилятор пытается решить, что он может отправлять статически, а что должен динамически отправлять, и он проваливается через пробел из-за ссылки Obj-C в вашем коде. Причина, по которой
dynamic
«работает», заключается в том, что он заставляет Obj-C связывать все, поэтому все всегда динамично.Так что использовать расширения для группировки - это нормально, это здорово, но неправильно переопределять расширения в расширениях. Любые переопределения должны быть в самом главном классе и обращаться к точкам расширения.
источник
supportedInterfaceOrientations
вUINavigationController
(для целей показаны различные виды в разных направлениях), вы должны использовать пользовательский класс не является расширением? Во многих ответах предлагается использовать расширение для переопределения,supportedInterfaceOrientations
но хотелось бы получить разъяснения. Спасибо!Существует способ добиться четкого разделения сигнатуры класса и реализации (в расширениях), сохраняя при этом возможность иметь переопределения в подклассах. Уловка состоит в том, чтобы использовать переменные вместо функций
Если вы убедитесь, что каждый подкласс определен в отдельном исходном файле Swift, вы можете использовать вычисляемые переменные для переопределений, сохраняя при этом соответствующую реализацию, четко организованную в расширениях. Это позволит обойти «правила» Swift и упорядочит API / подпись вашего класса в одном месте:
...
...
Расширения каждого класса могут использовать одни и те же имена методов для реализации, поскольку они являются частными и не видны друг другу (пока они находятся в отдельных файлах).
Как видите, наследование (с использованием имени переменной) работает правильно с использованием super.variablename
источник
Этот ответ не был направлен на OP, за исключением того факта, что я почувствовал вдохновение отреагировать на его утверждение: «Я стараюсь только помещать необходимые (сохраненные свойства, инициализаторы) в определения моих классов и перемещать все остальное в их собственное расширение. .. ". Я в первую очередь программист на C #, а в C # для этой цели можно использовать частичные классы. Например, Visual Studio помещает материалы, связанные с пользовательским интерфейсом, в отдельный исходный файл, используя частичный класс, и оставляет ваш основной исходный файл незагроможденным, чтобы вас не отвлекали.
Если вы выполните поиск по запросу «быстрый частичный класс», вы найдете различные ссылки, в которых приверженцы Swift говорят, что Swift не нуждается в частичных классах, потому что вы можете использовать расширения. Интересно, что если вы введете «быстрое расширение» в поле поиска Google, его первым предложением будет «переопределение быстрого расширения», и на данный момент этот вопрос о переполнении стека является первым хитом. Я считаю, что это означает, что проблемы с (отсутствием) возможностей переопределения являются наиболее востребованной темой, связанной с расширениями Swift, и подчеркивает тот факт, что расширения Swift не могут заменить частичные классы, по крайней мере, если вы используете производные классы в своих программирование.
В любом случае, чтобы сократить длинное введение, я столкнулся с этой проблемой в ситуации, когда я хотел переместить некоторые шаблоны / методы багажа из основных исходных файлов для классов Swift, которые генерировала моя программа C # -to-Swift. Столкнувшись с проблемой запрета переопределения для этих методов после их перемещения в расширения, я в итоге реализовал следующий простой обходной путь. Основные исходные файлы Swift по-прежнему содержат некоторые крошечные методы-заглушки, которые вызывают реальные методы в файлах расширений, и этим методам расширения присваиваются уникальные имена, чтобы избежать проблемы переопределения.
,
,
,
,
Как я уже сказал во введении, это на самом деле не отвечает на вопрос OP, но я надеюсь, что этот простой обходной путь может быть полезным для других, кто хочет переместить методы из основных исходных файлов в файлы расширений и столкнуться с проблемой no -Проблема переопределения.
источник
Используйте POP (протокол-ориентированное программирование) для переопределения функций в расширениях.
источник