Jestem programistą Java, który teraz współpracuje z Objective-C. Chciałbym stworzyć klasę abstrakcyjną, ale nie wydaje się to możliwe w Objective-C. czy to możliwe?
Jeśli nie, jak blisko klasy abstrakcyjnej mogę się zbliżyć do celu C?
objective-c
abstract-class
Jonathan Arbogast
źródło
źródło
Odpowiedzi:
Zazwyczaj klasa Objective-C jest abstrakcyjna tylko zgodnie z konwencją - jeśli autor dokumentuje klasę jako abstrakcyjną, po prostu nie używaj jej bez podklasy. Jednak nie ma wymuszania czasu kompilacji, które uniemożliwia tworzenie instancji klasy abstrakcyjnej. W rzeczywistości nie ma nic, co powstrzymałoby użytkownika od zapewnienia implementacji metod abstrakcyjnych za pośrednictwem kategorii (tj. W czasie wykonywania). Możesz zmusić użytkownika do przynajmniej przesłonięcia niektórych metod, zgłaszając wyjątek w implementacji tych metod w swojej klasie abstrakcyjnej:
Jeśli twoja metoda zwraca wartość, jest nieco łatwiejsza w użyciu
ponieważ wtedy nie musisz dodawać instrukcji return z metody
Jeśli klasa abstrakcyjna jest tak naprawdę interfejsem (tzn. Nie ma konkretnych implementacji metod), użycie protokołu Objective-C jest bardziej odpowiednią opcją.
źródło
@protocol
definicji, ale nie możesz zdefiniować tam metod.+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
działa to bardzo dobrze.doesNotRecognizeSelector
.Nie, nie ma możliwości stworzenia klasy abstrakcyjnej w Objective-C.
Możesz wyśmiewać klasę abstrakcyjną - wywołując metodę / selektor wywoływanie doesNotRecognizeSelector: i dlatego zgłaszaj wyjątek, który sprawia, że klasa nie nadaje się do użytku.
Na przykład:
Możesz to również zrobić dla init.
źródło
NSObject
odniesienie sugeruje użycie tego, gdy NIE chcesz dziedziczyć metody, a nie wymuszać zastąpienia metody. Chociaż może to być to samo, być może :)Po prostu riffuję powyższą odpowiedź @Barry Wark (i aktualizuję dla iOS 4.3) i zostawiam to dla własnego odniesienia:
wtedy w swoich metodach możesz tego użyć
Uwagi: Nie jestem pewien, czy sprawienie, aby makro wyglądało jak funkcja C, jest dobrym pomysłem, czy nie, ale zachowam je, dopóki nie nauczę się inaczej. Myślę, że bardziej poprawne jest używanie
NSInvalidArgumentException
(niżNSInternalInconsistencyException
), ponieważ to właśnie system wykonawczy rzuca w odpowiedzi nadoesNotRecognizeSelector
wywołanie (patrzNSObject
dokumentacja).źródło
universe.thing
ale się rozwija[Universe universe].thing
. Świetna zabawa, oszczędność tysięcy liter kodu ...#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.__PRETTY_FUNCTION__
wszędzie, za pomocą makra DLog (...), jak sugerowano tutaj: stackoverflow.com/a/969291/38557 .#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
przypadku, gdy zaproponowałem, że dostaniesz takżeHomeViewController - method not implemented
dziedziczoną z bazy HomeViewController - to poda więcej informacjiRozwiązanie, które wymyśliłem to:
W ten sposób kompilator wyświetli ostrzeżenie dla dowolnej metody protokołu, która nie jest zaimplementowana przez klasę podrzędną.
Nie jest tak zwięzły jak w Javie, ale dostajesz ostrzeżenie o żądanym kompilatorze.
źródło
NSObject
). Jeśli następnie umieścisz protokół i deklarację klasy podstawowej w tym samym pliku nagłówkowym, będzie to prawie nie do odróżnienia od klasy abstrakcyjnej.Z listy mailingowej Omni Group :
Cel C nie ma w tej chwili abstrakcyjnej konstrukcji kompilatora takiej jak Java.
Więc wystarczy zdefiniować klasę abstrakcyjną jak każdą inną normalną klasę i zaimplementować kody pośredniczące metod dla metod abstrakcyjnych, które są puste lub zgłaszają brak obsługi selektora. Na przykład...
Wykonuję również następujące czynności, aby zapobiec inicjalizacji klasy abstrakcyjnej za pomocą domyślnego inicjatora.
źródło
Zamiast próbować utworzyć abstrakcyjną klasę podstawową, rozważ użycie protokołu (podobnego do interfejsu Java). Umożliwia to zdefiniowanie zestawu metod, a następnie zaakceptowanie wszystkich obiektów zgodnych z protokołem i wdrożenie metod. Na przykład mogę zdefiniować protokół operacji, a następnie mieć taką funkcję:
Gdzie op może być dowolnym obiektem implementującym protokół operacji.
Jeśli potrzebujesz abstrakcyjnej klasy podstawowej, aby zrobić coś więcej niż tylko zdefiniować metody, możesz utworzyć zwykłą klasę Objective-C i zapobiec jej tworzeniu. Po prostu zastąp funkcję inicjującą - (id) i ustaw ją na zero lub assert (false). Nie jest to bardzo czyste rozwiązanie, ale ponieważ Objective-C jest w pełni dynamiczny, tak naprawdę nie ma bezpośredniego odpowiednika abstrakcyjnej klasy bazowej.
źródło
Ten wątek jest trochę stary i większość tego, co chcę udostępnić, już tu jest.
Jednak moja ulubiona metoda nie jest wymieniona, a AFAIK nie ma natywnego wsparcia w obecnym Clang, więc proszę…
Po pierwsze i najważniejsze (jak już zauważyli inni) klasy abstrakcyjne są czymś bardzo rzadkim w Objective-C - zamiast tego zwykle używamy kompozycji (czasem poprzez delegację). Jest to prawdopodobnie powód, dla którego taka funkcja nie istnieje jeszcze w języku / kompilatorze - oprócz
@dynamic
właściwości, które IIRC zostały dodane w ObjC 2.0 towarzyszącym wprowadzeniu CoreData.Ale biorąc pod uwagę, że (po dokładnej ocenie twojej sytuacji!) Doszedłeś do wniosku, że delegacja (lub ogólnie skład) nie nadaje się do rozwiązania twojego problemu, oto jak to zrobić:
[self doesNotRecognizeSelector:_cmd];
…__builtin_unreachable();
wyciszenie ostrzeżenia, które otrzymasz dla metod nie-void, mówiąc: „kontrola osiągnęła koniec funkcji nie-void bez powrotu”.-[NSObject doesNotRecognizeSelector:]
używając__attribute__((__noreturn__))
kategorii bez implementacji, aby nie zastąpić oryginalnej implementacji tej metody, i dołącz nagłówek tej kategorii do PCH twojego projektu.Osobiście wolę wersję makro, ponieważ pozwala mi to maksymalnie zmniejszyć płytę kotłową.
Oto on:
Jak widać, makro zapewnia pełną implementację metod abstrakcyjnych, redukując niezbędną ilość płyty kotłowej do absolutnego minimum.
Jeszcze lepszą opcją byłoby lobbowanie zespołu Clanga celu udostępnienia atrybutu kompilatora w tym przypadku za pośrednictwem żądań funkcji. (Lepiej, ponieważ umożliwiłoby to także diagnostykę w czasie kompilacji dla scenariuszy, w których podklasę, np. NSIncrementalStore.)
Dlaczego wybieram tę metodę?
__builtin_unreachable()
może zaskoczyć ludzi, ale też łatwo to zrozumieć).Ten ostatni punkt wymaga chyba wyjaśnienia:
Niektóre (większość?) Osoby usuwają twierdzenia w kompilacjach wersji. (Nie zgadzam się z tym nawykiem, ale to już inna historia…) Niewdrożenie wymaganej metody - jednakże - jest złe , straszne , złe i w zasadzie koniec wszechświata dla twojego programu. Twój program nie może działać poprawnie pod tym względem, ponieważ jest niezdefiniowany, a niezdefiniowane zachowanie jest najgorszą rzeczą w historii. Stąd możliwość pozbawienia tej diagnostyki bez generowania nowej diagnostyki byłaby całkowicie niedopuszczalna.
Wystarczająco źle, że nie można uzyskać prawidłowej diagnostyki w czasie kompilacji dla takich błędów programisty i trzeba uciekać się do ich wykrywania w czasie wykonywania, ale jeśli można je pomijać w kompilacjach wersji, dlaczego warto mieć klasę abstrakcyjną w pierwsze miejsce?
źródło
__builtin_unreachable();
klejnot sprawia, że działa idealnie. Makro sprawia, że jest ono samo dokumentujące, a zachowanie pasuje do tego, co dzieje się, jeśli wywołasz brakującą metodę na obiekcie.Korzystanie
@property
i@dynamic
może również działać. Jeśli zadeklarujesz właściwość dynamiczną i nie podasz pasującej implementacji metody, wszystko będzie nadal kompilowane bez ostrzeżeń, aunrecognized selector
podczas próby uzyskania dostępu pojawi się błąd w czasie wykonywania. To w zasadzie to samo, co dzwonienie[self doesNotRecognizeSelector:_cmd]
, ale przy znacznie mniejszym pisaniu.źródło
W Xcode (używając clang itp.) Lubię używać
__attribute__((unavailable(...)))
do oznaczania klas abstrakcyjnych, aby otrzymać błąd / ostrzeżenie, jeśli spróbujesz go użyć.Zapewnia pewną ochronę przed przypadkowym użyciem tej metody.
Przykład
W
@interface
metce klasy podstawowej metody „abstrakcyjne”:Idąc krok dalej, tworzę makro:
To pozwala ci to zrobić:
Jak powiedziałem, nie jest to prawdziwa ochrona kompilatora, ale jest tak dobra, jak będziesz w języku, który nie obsługuje metod abstrakcyjnych.
źródło
-init
metodę w swojej podklasie.Odpowiedź na pytanie jest rozproszona w komentarzach pod już udzielonymi odpowiedziami. Podsumowuję i upraszczam tutaj.
Opcja 1: Protokoły
Jeśli chcesz utworzyć klasę abstrakcyjną bez implementacji, użyj „Protokołów”. Klasy dziedziczące protokół są zobowiązane do implementacji metod w protokole.
Opcja 2: wzorzec metody szablonu
Jeśli chcesz utworzyć klasę abstrakcyjną z częściową implementacją, taką jak „wzorzec metody szablonu”, jest to rozwiązanie. Cel C - Wzorzec metod szablonów?
źródło
Kolejna alternatywa
Po prostu sprawdź klasę w klasie Abstract i Assert lub Exception, cokolwiek ci się spodoba.
Eliminuje to konieczność nadpisywania
init
źródło
(więcej powiązanych sugestii)
Chciałem mieć sposób, aby poinformować programistę o tym, że „nie dzwoń od dziecka” i całkowicie go zastąpić (w moim przypadku nadal oferuję domyślną funkcjonalność w imieniu rodzica, gdy nie jest rozszerzony):
Zaletą jest to, że programista WIDZIE „nadpisanie” w deklaracji i będzie wiedział, że nie powinien wywoływać
[super ..]
.To prawda, że definiowanie poszczególnych typów zwrotów jest brzydkie, ale służy to za dobrą wskazówkę wizualną i nie można łatwo użyć części „override_” w definicji podklasy.
Oczywiście klasa może nadal mieć domyślną implementację, gdy rozszerzenie jest opcjonalne. Ale jak mówią inne odpowiedzi, w razie potrzeby zaimplementuj wyjątek czasu wykonywania, na przykład dla klas abstrakcyjnych (wirtualnych).
Byłoby miło mieć wbudowane podpowiedzi kompilatora takie jak ta, nawet podpowiedzi, kiedy najlepiej jest pre / post wywołać narzędzie super, zamiast przeglądać komentarze / dokumentację lub ... zakładać.
źródło
Jeśli jesteś przyzwyczajony do kompilatora wykrywającego abstrakcyjne naruszenia instancji w innych językach, zachowanie Objective-C jest rozczarowujące.
Jako późno wiążący język jasne jest, że Objective-C nie może podejmować statycznych decyzji, czy klasa jest naprawdę abstrakcyjna, czy nie (możesz dodawać funkcje w czasie wykonywania ...), ale w typowych przypadkach użycia wydaje się to niedociągnięciem. Wolałbym, aby kompilator całkowicie zapobiegał tworzeniu instancji klas abstrakcyjnych zamiast zgłaszania błędu w czasie wykonywania.
Oto wzorzec, którego używamy do uzyskania tego rodzaju sprawdzania statycznego za pomocą kilku technik ukrywania inicjatorów:
źródło
Prawdopodobnie tego rodzaju sytuacje powinny się zdarzyć tylko w czasie programowania, więc może to działać:
źródło
Możesz użyć metody zaproponowanej przez @Yar (z pewnymi modyfikacjami):
Tutaj otrzymasz wiadomość typu:
Lub stwierdzenie:
W takim przypadku otrzymasz:
Możesz także używać protokołów i innych rozwiązań - ale jest to jedno z najprostszych.
źródło
Kakao nie zapewnia niczego zwanego abstrakcyjnym. Możemy stworzyć streszczenie klasy, które jest sprawdzane tylko w czasie wykonywania, a podczas kompilacji nie jest sprawdzane.
źródło
Zwykle po prostu wyłączam metodę init w klasie, którą chcę wyodrębnić:
To generuje błąd w czasie kompilacji za każdym razem, gdy wywołasz init w tej klasie. Następnie używam metod klasowych do wszystkiego innego.
Cel C nie ma wbudowanego sposobu deklarowania klas abstrakcyjnych.
źródło
Zmieniając nieco to, co sugeruje @redfood, stosując komentarz @ dotToString, faktycznie masz rozwiązanie przyjęte przez IGListKit na Instagramie .
AbstractClass
musi być wprowadzane lub wyprowadzane jakąś metodą, wpisz jeAbstractClass<Protocol>
zamiast tego.Ponieważ
AbstractClass
nie jest implementowanyProtocol
, jedynym sposobem na utworzenieAbstractClass<Protocol>
instancji jest podklasowanie. PonieważAbstractClass
sam nie może być użyty nigdzie w projekcie, staje się abstrakcyjny.Oczywiście nie przeszkadza to niezalecanym programistom dodawać nowe metody odwołujące się do nich
AbstractClass
, co w końcu pozwoliłoby na wystąpienie (już nie) abstrakcyjnej klasy.Przykład ze świata rzeczywistego: IGListKit ma klasę podstawową,
IGListSectionController
która nie implementuje protokołuIGListSectionType
, jednak każda metoda, która wymaga wystąpienia tej klasy, faktycznie pyta o typIGListSectionController<IGListSectionType>
. Dlatego nie ma możliwości użycia obiektu typuIGListSectionController
do czegokolwiek przydatnego w ich ramach.źródło
W rzeczywistości Objective-C nie ma klas abstrakcyjnych, ale można użyć protokołów, aby osiągnąć ten sam efekt. Oto próbka:
CustomProtocol.h
TestProtocol.h
TestProtocol.m
źródło
Prosty przykład tworzenia klasy abstrakcyjnej
źródło
Nie możesz po prostu stworzyć delegata?
Delegat jest jak abstrakcyjna klasa podstawowa w tym sensie, że mówisz, jakie funkcje należy zdefiniować, ale tak naprawdę ich nie definiujesz.
Następnie za każdym razem, gdy implementujesz swojego delegata (tj. Klasę abstrakcyjną), kompilator ostrzega cię przed opcjonalnymi i obowiązkowymi funkcjami, dla których musisz zdefiniować zachowanie.
Dla mnie to brzmi jak abstrakcyjna klasa podstawowa.
źródło