Переопределение методов в расширениях Swift

Я стараюсь только поместить необходимые вещи (хранимые свойства, инициализаторы) в свои определения classов и переместить все остальное в свое собственное extension , вроде как extension на каждый логический блок, который я бы группировал с // MARK: также.

Например, для подclassа UIView я получаю расширение для материалов, связанных с компоновкой, один для подписки и обработки событий и т. Д. В этих расширениях я неизбежно должен переопределить некоторые методы UIKit, например layoutSubviews . Я никогда не замечал никаких проблем с этим подходом – до сегодняшнего дня.

Возьмите эту иерархию classов, например:

 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() 

Выход – ABC . Для меня это мало смысла. Я читал о статических сообщениях о расширениях протокола, но это не протокол. Это обычный class, и я ожидаю, что вызовы методов будут динамически отправляться во время выполнения. Очевидно, что вызов на C должен, по крайней мере, быть динамически отправлен и производить C ?

Если я удалю наследование из NSObject и сделаю C корневым classом, компилятор жалуется, что declarations in extensions cannot override yet , о которых я уже читал. Но каким образом NSObject как корневой class меняет ситуацию?

Перемещение обоих переопределений в их объявление classа дает AAA как ожидалось, перемещение только B производит ABB , только перемещение только A производит CBC , последний из которых не имеет для меня никакого смысла: даже тот, который статически вводится в A создает A -выход!

Добавление ключевого слова dynamic в определение или переопределение, похоже, дает мне желаемое поведение «с этой точки иерархии classов вниз» …

Давайте изменим наш пример на нечто менее сконструированное, что на самом деле заставило меня опубликовать этот вопрос:

 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() 

Теперь мы получаем ABA . Здесь я не могу сделать отображение layoutSubviews в UIView любым способом.

Перемещение обоих переопределений в декларацию classа возвращает нам AAA , только A или только B все еще получают нас ABA . dynamic снова решает мои проблемы.

Теоретически я мог бы добавить dynamic во все, что я делаю, но чувствую, что я делаю что-то еще не так.

Действительно ли неправильно использовать extension s для группировки кода, как я?

    Расширения не могут / не должны отменяться.

    Невозможно переопределить функциональность (например, свойства или методы) в расширениях, как описано в Руководстве Swift от Apple.

    Расширения могут добавлять новые функции к типу, но они не могут переопределять существующие функции.

    Руководство разработчика Apple

    Компилятор позволяет вам переопределить расширение для совместимости с Objective-C. Но это фактически нарушает языковые директивы.

    «Это просто напомнило мне« Три закона робототехники »Исаака Азимова 🤖

    Расширения ( синтаксический сахар ) определяют независимые методы, которые получают свои собственные аргументы. Функция, которая вызывается для ie layoutSubviews зависит от контекста, который компилятор знает о компиляции кода. UIView наследует от UIResponder, который наследует от NSObject, поэтому переопределение в расширении разрешено, но не должно быть .

    Поэтому нет ничего плохого в группировке, но вы должны переопределить class, а не расширение.

    Примечания к Директиве

    Вы можете override метод суперclassа, т.е. load() initialize() в расширении подclassа, если метод совместим с Objective-C.

    Поэтому мы можем взглянуть на то, почему он позволяет вам скомпилировать с помощью layoutSubviews .

    Все приложения Swift выполняются внутри среды выполнения Objective-C, за исключением тех случаев, когда используются только чистые среды Swift, которые позволяют использовать среду только для Swift.

    Когда мы выяснили, что время выполнения Objective-C обычно вызывает два основных метода main load() и initialize() автоматически при инициализации classов в процессах вашего приложения.

    Что касается dynamic модификатора

    Из библиотеки разработчиков iOS

    Вы можете использовать dynamic модификатор, чтобы требовать, чтобы доступ к членам был динамически отправлен через среду выполнения Objective-C.

    Когда Swift API импортируются с помощью среды выполнения Objective-C, нет никаких гарантий динамической отправки свойств, методов, индексов или инициализаторов. Компилятор Swift может по-прежнему девиртуализировать или встроить членский доступ для оптимизации производительности вашего кода, минуя время выполнения Objective-C. 😳

    Таким образом, dynamic может применяться к вашему layoutSubviews -> UIView Class поскольку он представлен Objective-C, и доступ к этому члену всегда используется с использованием среды выполнения Objective-C.

    Вот почему компилятор позволяет вам использовать override и dynamic .

    Одной из целей Swift является статическая диспетчеризация, а точнее сокращение динамической диспетчеризации. Obj-C, однако, является очень динамичным языком. Ситуация, о которой вы видите, подтверждается связью между двумя языками и тем, как они работают вместе. Он не должен компилироваться.

    Одним из основных моментов в расширениях является то, что они предназначены для расширения, а не для замены / переопределения. Из названия и документации ясно, что это намерение. Действительно, если вы вытащите ссылку на Obj-C из своего кода (удалите NSObject как суперclass), он не будет компилироваться.

    Итак, компилятор пытается решить, что он может статически отправлять и что ему нужно динамически отправлять, и он проваливается через разрыв из-за ссылки Obj-C в вашем коде. Причина, по которой dynamic «работает» заключается в том, что он заставляет Obj-C связываться со всем, так что все это всегда динамично.

    Таким образом, не стоит использовать расширения для группировки, это здорово, но неверно переопределять расширения. Любые переопределения должны быть в основном classе и вызывать точки расширения.

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

    Если вы обязательно определяете каждый подclass в отдельном быстром исходном файле, вы можете использовать вычисленные переменные для переопределений, сохраняя при этом соответствующую реализацию, которая была бы аккуратно организована в расширениях. Это обойдется «правилам» Swift и сделает ваш API / подпись вашего classа аккуратно организованным в одном месте:

      // ---------- BaseClass.swift ------------- public class BaseClass { public var method1:(Int) -> String { return doMethod1 } public init() {} } // the extension could also be in a separate file extension BaseClass { private func doMethod1(param:Int) -> String { return "BaseClass \(param)" } } 

      // ---------- ClassA.swift ---------- public class A:BaseClass { override public var method1:(Int) -> String { return doMethod1 } } // this extension can be in a separate file but not in the same // file as the BaseClass extension that defines its doMethod1 implementation extension A { private func doMethod1(param:Int) -> String { return "A \(param) added to \(super.method1(param))" } } 

      // ---------- ClassB.swift ---------- public class B:A { override public var method1:(Int) -> String { return doMethod1 } } extension B { private func doMethod1(param:Int) -> String { return "B \(param) added to \(super.method1(param))" } } 

    Расширение каждого classа может использовать одни и те же имена методов для реализации, поскольку они являются частными и не видны друг другу (если они находятся в отдельных файлах).

    Как вы видите, наследование (с использованием имени переменной) работает правильно, используя super.variablename

      BaseClass().method1(123) --> "BaseClass 123" A().method1(123) --> "A 123 added to BaseClass 123" B().method1(123) --> "B 123 added to A 123 added to BaseClass 123" (B() as A).method1(123) --> "B 123 added to A 123 added to BaseClass 123" (B() as BaseClass).method1(123) --> "B 123 added to A 123 added to BaseClass 123" 

    Этот ответ он не нацелился на ОП, кроме того, что я чувствовал себя вдохновленным ответом на его высказывание: «Я стараюсь только поставить предметы первой необходимости (хранимые свойства, инициализаторы) в свои определения classов и переместить все остальное в их собственное расширение. .. “. Я в первую очередь программист на C #, а на C # для этой цели можно использовать частичные classы. Например, Visual Studio помещает материал, связанный с пользовательским интерфейсом, в отдельный исходный файл с использованием частичного classа и оставляет ваш основной исходный файл незагроможденным, чтобы у вас не было этого отвлечения.

    Если вы ищете «быстрый частичный class», вы найдете различные ссылки, в которых участники Swift говорят, что Swift не нуждается в частичных classах, потому что вы можете использовать расширения. Интересно, что если вы наберете «быстрое расширение» в поле поиска Google, его первое предложение поиска – «быстрое переопределение расширения», и на данный момент этот вопрос с переполнением стека является первым хитом. Я полагаю, что это означает, что проблемы с (недостатком) возможностей переопределения являются наиболее просматриваемыми темами, связанными с расширениями Swift, и подчеркивает тот факт, что расширения Swift не могут заменить частичные classы, по крайней мере, если вы используете производные classы в своем программирование.

    Во всяком случае, чтобы сократить короткое введение, я столкнулся с этой проблемой в ситуации, когда я хотел переместить некоторые методы шаблонов / багажа из основных исходных файлов для classов Swift, которые генерировала моя программа C # -to-Swift. Запустив проблему без переопределения, разрешенную для этих методов после перемещения их к расширениям, я закончил реализацию следующего простого подхода. Основные исходные файлы Swift по-прежнему содержат некоторые крошечные методы заглушки, которые вызывают реальные методы в файлах расширений, и этим методам расширения присваиваются уникальные имена, чтобы избежать проблемы с переопределением.

     public protocol PCopierSerializable { static func getFieldTable(mCopier : MCopier) -> FieldTable static func createObject(initTable : [Int : Any?]) -> Any func doSerialization(mCopier : MCopier) } 

    ,

     public class SimpleClass : PCopierSerializable { public var aMember : Int32 public init( aMember : Int32 ) { self.aMember = aMember } public class func getFieldTable(mCopier : MCopier) -> FieldTable { return getFieldTable_SimpleClass(mCopier: mCopier) } public class func createObject(initTable : [Int : Any?]) -> Any { return createObject_SimpleClass(initTable: initTable) } public func doSerialization(mCopier : MCopier) { doSerialization_SimpleClass(mCopier: mCopier) } } 

    ,

     extension SimpleClass { class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable { var fieldTable : FieldTable = [ : ] fieldTable[376442881] = { () in try mCopier.getInt32A() } // aMember return fieldTable } class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any { return SimpleClass( aMember: initTable[376442881] as! Int32 ) } func doSerialization_SimpleClass(mCopier : MCopier) { mCopier.writeBinaryObjectHeader(367620, 1) mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } ) } } 

    ,

     public class DerivedClass : SimpleClass { public var aNewMember : Int32 public init( aNewMember : Int32, aMember : Int32 ) { self.aNewMember = aNewMember super.init( aMember: aMember ) } public class override func getFieldTable(mCopier : MCopier) -> FieldTable { return getFieldTable_DerivedClass(mCopier: mCopier) } public class override func createObject(initTable : [Int : Any?]) -> Any { return createObject_DerivedClass(initTable: initTable) } public override func doSerialization(mCopier : MCopier) { doSerialization_DerivedClass(mCopier: mCopier) } } 

    ,

     extension DerivedClass { class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable { var fieldTable : FieldTable = [ : ] fieldTable[376443905] = { () in try mCopier.getInt32A() } // aNewMember fieldTable[376442881] = { () in try mCopier.getInt32A() } // aMember return fieldTable } class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any { return DerivedClass( aNewMember: initTable[376443905] as! Int32, aMember: initTable[376442881] as! Int32 ) } func doSerialization_DerivedClass(mCopier : MCopier) { mCopier.writeBinaryObjectHeader(367621, 2) mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } ) mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } ) } } 

    Как я уже сказал в своем вступительном слове, на самом деле это не отвечает на вопрос OP, но я надеюсь, что этот простой подход может быть полезен другим, кто хочет переместить методы из основных исходных файлов в файлы расширений и запустить в no -определить проблему.

    Используйте POP (протокол-ориентированное программирование) для переопределения функций в расширениях.

     protocol AProtocol { func aFunction() } extension AProtocol { func aFunction() { print("empty") } } class AClass: AProtocol { } extension AClass { func aFunction() { print("not empty") } } let cls = AClass() cls.aFunction() 
    Давайте будем гением компьютера.