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?
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?
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:
[NSException raise:NSInternalInconsistencyException
format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
Jeśli twoja metoda zwraca wartość, jest nieco łatwiejsza w użyciu
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
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ą.
@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:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Możesz to również zrobić dla init.
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:
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()
wtedy w swoich metodach możesz tego użyć
- (void) someMethod {
mustOverride(); // or methodNotImplemented(), same thing
}
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 na doesNotRecognizeSelector
wywołanie (patrz NSObject
dokumentacja).
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że HomeViewController - method not implemented
dziedziczoną z bazy HomeViewController - to poda więcej informacji
Rozwią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.
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...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Wykonuję również następujące czynności, aby zapobiec inicjalizacji klasy abstrakcyjnej za pomocą domyślnego inicjatora.
- (id)init
{
[self doesNotRecognizeSelector:_cmd];
[self release];
return nil;
}
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ę:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
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.
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:
// Definition:
#define D12_ABSTRACT_METHOD {\
[self doesNotRecognizeSelector:_cmd]; \
__builtin_unreachable(); \
}
// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString
#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD
#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
if (aRange.location + aRange.length >= [self length])
[NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];
unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
[self getCharacters:buffer range:aRange];
return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…
@end
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.)
__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?
__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ń, a unrecognized 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.
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.
W @interface
metce klasy podstawowej metody „abstrakcyjne”:
- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));
Idąc krok dalej, tworzę makro:
#define UnavailableMacro(msg) __attribute__((unavailable(msg)))
To pozwala ci to zrobić:
- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");
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.
-init
metodę w swojej podklasie.
Odpowiedź na pytanie jest rozproszona w komentarzach pod już udzielonymi odpowiedziami. Podsumowuję i upraszczam tutaj.
Jeśli chcesz utworzyć klasę abstrakcyjną bez implementacji, użyj „Protokołów”. Klasy dziedziczące protokół są zobowiązane do implementacji metod w protokole.
@protocol ProtocolName
// list of methods and properties
@end
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?
Kolejna alternatywa
Po prostu sprawdź klasę w klasie Abstract i Assert lub Exception, cokolwiek ci się spodoba.
@implementation Orange
- (instancetype)init
{
self = [super init];
NSAssert([self class] != [Orange class], @"This is an abstract class");
if (self) {
}
return self;
}
@end
Eliminuje to konieczność nadpisywania init
(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):
typedef void override_void;
typedef id override_id;
@implementation myBaseClass
// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;
// some internally required default behavior
- (void) doesSomethingImportant;
@end
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ć.
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:
//
// Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));
@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end
@interface Base : NSObject {
@protected
__weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}
- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end
//
// Base.m
#import "Base.h"
// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end
@implementation Base
- (instancetype)initFromDerived {
// It is unlikely that this becomes incorrect, but assert
// just in case.
NSAssert(![self isMemberOfClass:[Base class]],
@"To be called only from derived classes!");
self = [super init];
return self;
}
- (void) doStuffUsingDependentFunction {
[_protocolHelper dependentFunction]; // Use it
}
@end
//
// Derived.h
#import "Base.h"
@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end
//
// Derived.m
#import "Derived.h"
// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end
// Privately inherit protocol
@interface Derived () <MyProtocol>
@end
@implementation Derived
-(instancetype) initDerived {
self= [super initFromDerived];
if (self) {
self->_protocolHelper= self;
}
return self;
}
// Implement the missing function
-(void)dependentFunction {
}
@end
Możesz użyć metody zaproponowanej przez @Yar (z pewnymi modyfikacjami):
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Tutaj otrzymasz wiadomość typu:
<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'
Lub stwierdzenie:
NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");
W takim przypadku otrzymasz:
<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53
Możesz także używać protokołów i innych rozwiązań - ale jest to jedno z najprostszych.
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.
Zwykle po prostu wyłączam metodę init w klasie, którą chcę wyodrębnić:
- (instancetype)__unavailable init; // This is an abstract class.
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.
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 je AbstractClass<Protocol>
zamiast tego.Ponieważ AbstractClass
nie jest implementowany Protocol
, jedynym sposobem na utworzenie AbstractClass<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łu IGListSectionType
, jednak każda metoda, która wymaga wystąpienia tej klasy, faktycznie pyta o typ IGListSectionController<IGListSectionType>
. Dlatego nie ma możliwości użycia obiektu typu IGListSectionController
do czegokolwiek przydatnego w ich ramach.
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:
#import <Foundation/Foundation.h>
@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end
#import <Foundation/Foundation.h>
#import "CustomProtocol.h"
@interface TestProtocol : NSObject <CustomProtocol>
@end
#import "TestProtocol.h"
@implementation TestProtocol
- (void)methodA
{
NSLog(@"methodA...");
}
- (void)methodB
{
NSLog(@"methodB...");
}
@end
Prosty przykład tworzenia klasy abstrakcyjnej
// Declare a protocol
@protocol AbcProtocol <NSObject>
-(void)fnOne;
-(void)fnTwo;
@optional
-(void)fnThree;
@end
// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>
@end
@implementation AbstractAbc
-(id)init{
self = [super init];
if (self) {
}
return self;
}
-(void)fnOne{
// Code
}
-(void)fnTwo{
// Code
}
@end
// Implementation class
@interface ImpAbc : AbstractAbc
@end
@implementation ImpAbc
-(id)init{
self = [super init];
if (self) {
}
return self;
}
// You may override it
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}
-(void)fnThree{
// Code
}
@end
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.