Mam tendencję do umieszczania tylko niezbędnych elementów (przechowywanych właściwości, inicjatorów) w definicjach moich klas i przenoszę wszystko inne do ich własnych extension
, podobnie jak w przypadku extension
bloku logicznego, z // MARK:
którym również bym się grupował.
Na przykład w przypadku podklasy UIView chciałbym otrzymać rozszerzenie do rzeczy związanych z układem, jedno do subskrybowania i obsługi zdarzeń i tak dalej. W tych rozszerzeniach nieuchronnie muszę przesłonić niektóre metody UIKit, np layoutSubviews
. Nigdy nie zauważyłem żadnych problemów z tym podejściem - do dzisiaj.
Weźmy na przykład tę hierarchię klas:
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()
Wynik jest A B C
. To nie ma dla mnie sensu. Czytałem o statycznym wysyłaniu rozszerzeń protokołów, ale to nie jest protokół. Jest to zwykła klasa i oczekuję, że wywołania metod będą dynamicznie wysyłane w czasie wykonywania. Oczywiście wezwanie C
powinno być przynajmniej dynamicznie wysyłane i produkować C
?
Jeśli usunę dziedziczenie z NSObject
i C
utworzę klasę root, kompilator narzeka, mówiąc declarations in extensions cannot override yet
, o czym już czytałem. Ale w jaki sposób posiadanie NSObject
klasy głównej zmienia rzeczy?
Przenoszenie zarówno przesłonięcia do ich deklaracji klasy produkuje A A A
zgodnie z oczekiwaniami, poruszając tylko B
„s produkuje A B B
, poruszając tylko A
” s produkuje C B C
, z których ostatni ma absolutnie żadnego sensu dla mnie: nie nawet jeden statycznie wpisane do A
produkuje A
-Output więcej!
dynamic
Wydaje się, że dodanie słowa kluczowego do definicji lub zastąpienia daje mi pożądane zachowanie `` od tego punktu w hierarchii klas w dół '' ...
Zmieńmy nasz przykład na coś nieco mniej skonstruowanego, co właściwie skłoniło mnie do postawienia tego pytania:
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()
Teraz mamy A B A
. Tutaj nie mogę w żaden sposób nadać dynamiki layoutSubviews UIView.
Przeniesienie obu nadpisań do ich deklaracji klasy daje nam A A A
ponownie, tylko A lub tylko B nadal nas dostaje A B A
. dynamic
znowu rozwiązuje moje problemy.
Teoretycznie mógłbym dodać dynamic
do wszystkiego, override
co kiedykolwiek robię, ale czuję, że robię tu coś innego źle.
Czy naprawdę źle jest używać extension
s do grupowania kodu, tak jak ja?
źródło
Odpowiedzi:
Rozszerzenia nie mogą / nie powinny zastępować.
Nie można zastąpić funkcji (takich jak właściwości lub metody) w rozszerzeniach, zgodnie z dokumentacją w przewodniku Apple Swift Guide.
Szybki przewodnik programisty
Kompilator umożliwia przesłonięcie rozszerzenia w celu zapewnienia zgodności z celem-C. Ale w rzeczywistości narusza dyrektywę językową.
😊To właśnie przypomniało mi „ Trzy prawa robotyki ” Isaaca Asimova 🤖
Rozszerzenia ( cukier syntaktyczny ) definiują niezależne metody, które otrzymują własne argumenty. Funkcja, dla której jest wywoływana, np.
layoutSubviews
Zależy od kontekstu, o którym kompilator wie, kiedy kompilowany jest kod. UIView dziedziczy po UIResponder, który dziedziczy po NSObject, więc nadpisanie w rozszerzeniu jest dozwolone, ale nie powinno .Więc nie ma nic złego w grupowaniu, ale powinieneś nadpisywać w klasie, a nie w rozszerzeniu.
Uwagi dyrektywy
Możesz tylko
override
metodę nadklasy, tj.load()
initialize()
Jako rozszerzenie podklasy, jeśli metoda jest kompatybilna z celem-C.Dlatego możemy przyjrzeć się, dlaczego pozwala na kompilację przy użyciu
layoutSubviews
.Wszystkie aplikacje Swift działają w środowisku uruchomieniowym Objective-C, z wyjątkiem sytuacji, gdy używają czystych frameworków tylko do Swift, które pozwalają na środowisko uruchomieniowe tylko do Swift.
Jak dowiedzieliśmy się, środowisko uruchomieniowe Objective-C zwykle wywołuje dwie główne metody klas
load()
iinitialize()
automatycznie podczas inicjowania klas w procesach aplikacji.Odnośnie
dynamic
modyfikatoraZ biblioteki programistów Apple (archive.org)
Możesz użyć
dynamic
modyfikatora, aby wymagać, aby dostęp do elementów członkowskich był dynamicznie przydzielany przez środowisko wykonawcze Objective-C.Gdy interfejsy API Swift są importowane przez środowisko uruchomieniowe Objective-C, nie ma gwarancji dynamicznego wysyłania właściwości, metod, indeksów ani inicjatorów. Kompilator Swift może nadal zdewirtualizować lub wbudowany dostęp do elementów członkowskich, aby zoptymalizować wydajność kodu, pomijając środowisko uruchomieniowe Objective-C. 😳
Więc
dynamic
może być zastosowane do twojegolayoutSubviews
->,UIView Class
ponieważ jest reprezentowane przez Objective-C, a dostęp do tego elementu jest zawsze używany przy użyciu środowiska uruchomieniowego Objective-C.Dlatego kompilator umożliwiający używanie
override
idynamic
.źródło
Jednym z celów Swift jest wysyłanie statyczne, a raczej redukcja dynamicznego wysyłania. Jednak Obj-C jest bardzo dynamicznym językiem. Sytuacja, którą widzisz, ma swoje źródło w związku między tymi dwoma językami i ich wzajemnej współpracy. Tak naprawdę nie powinno się kompilować.
Jednym z głównych punktów dotyczących rozszerzeń jest to, że służą one do rozszerzania, a nie zastępowania / zastępowania. Z nazwy i dokumentacji jasno wynika, że taki jest zamiar. Rzeczywiście, jeśli usuniesz łącze do Obj-C ze swojego kodu (usuń
NSObject
jako nadklasę), nie będzie się on kompilował.Tak więc kompilator próbuje zdecydować, co może wysłać statycznie, a co dynamicznie, i przechodzi przez lukę z powodu łącza Obj-C w kodzie. Powód
dynamic
`` działa '' jest taki, że wymusza łączenie Obj-C na wszystkim, więc wszystko jest zawsze dynamiczne.Tak więc nie jest złe używanie rozszerzeń do grupowania, to świetnie, ale nadpisywanie w rozszerzeniach jest błędem. Wszelkie zastąpienia powinny znajdować się w samej klasie głównej i odwoływać się do punktów rozszerzeń.
źródło
supportedInterfaceOrientations
wUINavigationController
(w celu pokazania różnych widoków w różnych orientacjach), powinieneś użyć klasy niestandardowej, a nie rozszerzenia? Wiele odpowiedzi sugeruje użycie rozszerzenia do zastąpienia,supportedInterfaceOrientations
ale chciałoby się wyjaśnić. Dzięki!Istnieje sposób na osiągnięcie czystego oddzielenia podpisu klasy i implementacji (w rozszerzeniach) przy jednoczesnym zachowaniu możliwości nadpisań w podklasach. Sztuczka polega na użyciu zmiennych zamiast funkcji
Jeśli upewnisz się, że zdefiniujesz każdą podklasę w oddzielnym, szybkim pliku źródłowym, możesz użyć obliczonych zmiennych do przesłonięć, zachowując odpowiednią organizację w rozszerzeniach. Spowoduje to obejście „reguł” języka Swift i sprawi, że interfejs API / podpis Twojej klasy będzie uporządkowany w jednym miejscu:
// ---------- 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))" } }
Rozszerzenia każdej klasy mogą używać tych samych nazw metod dla implementacji, ponieważ są one prywatne i nie są dla siebie widoczne (o ile znajdują się w oddzielnych plikach).
Jak widać, dziedziczenie (używając nazwy zmiennej) działa poprawnie przy użyciu 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"
źródło
Ta odpowiedź nie była skierowana do OP, poza faktem, że zainspirowało mnie jego stwierdzenie: „Zwykle umieszczam tylko niezbędne elementy (przechowywane właściwości, inicjatory) w definicjach moich klas, a wszystko inne przenoszę do ich własnego rozszerzenia. .. ”. Jestem przede wszystkim programistą C # iw C # można do tego celu używać klas częściowych. Na przykład program Visual Studio umieszcza elementy związane z interfejsem użytkownika w oddzielnym pliku źródłowym przy użyciu klasy częściowej i pozostawia główny plik źródłowy niezakłócony, aby nie rozpraszać uwagi.
Jeśli szukasz „szybkiej klasy częściowej”, znajdziesz różne linki, w których zwolennicy języka Swift mówią, że język Swift nie potrzebuje klas częściowych, ponieważ możesz używać rozszerzeń. Co ciekawe, jeśli wpiszesz „szybkie rozszerzenie” w polu wyszukiwania Google, jego pierwsza sugestia wyszukiwania brzmi „szybkie zastąpienie rozszerzenia”, a w tej chwili to pytanie przepełnienia stosu jest pierwszym trafieniem. Rozumiem przez to, że problemy z (brakiem) możliwości nadpisywania są najczęściej poszukiwanym tematem związanym z rozszerzeniami Swift i podkreśla fakt, że rozszerzenia Swift nie mogą zastąpić klas częściowych, przynajmniej jeśli używasz klas pochodnych w swoim programowanie.
W każdym razie, aby skrócić rozwlekłe wprowadzenie, natknąłem się na ten problem w sytuacji, w której chciałem przenieść niektóre metody standardowe / bagażowe z głównych plików źródłowych klas Swift, które generował mój program C # -do-Swift. Po napotkaniu problemu braku możliwości przesłonięcia tych metod po przeniesieniu ich do rozszerzeń, w końcu zaimplementowałem następujące proste obejście. Główne pliki źródłowe Swift nadal zawierają drobne metody pośredniczące, które wywołują prawdziwe metody w plikach rozszerzeń, a te metody rozszerzające mają unikalne nazwy, aby uniknąć problemu z przesłonięciem.
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) } ) } }
Jak powiedziałem we wstępie, to tak naprawdę nie odpowiada na pytanie OP, ale mam nadzieję, że to proste obejście może być pomocne dla innych, którzy chcą przenieść metody z głównych plików źródłowych do plików rozszerzeń i uruchomić - nadpisać problem.
źródło
Użyj POP (programowanie zorientowane na protokół), aby przesłonić funkcje w rozszerzeniach.
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()
źródło