Tworzenie klasy abstrakcyjnej w Objective-C


507

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?


18
Odpowiedzi poniżej są świetne. Uważam, że kwestia klas abstrakcyjnych jest stycznie związana z metodami prywatnymi - obie są metodami ograniczającymi możliwości kodu klienta i żadna z nich nie istnieje w Objective-C. Myślę, że pomaga zrozumieć, że mentalność samego języka zasadniczo różni się od Javy. Zobacz moją odpowiedź na: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor

Dzięki za informacje o mentalności społeczności Objective-C w przeciwieństwie do innych języków. To naprawdę rozwiązuje wiele powiązanych pytań, które miałem (np. Dlaczego nie ma prostego mechanizmu dla prywatnych metod itp.).
Jonathan Arbogast

1
więc spójrz na stronę CocoaDev, która daje porównanie java cocoadev.com/index.pl?AbstractSuperClass

2
Chociaż Barry wspomina o tym jako myśl następczą (wybacz mi, jeśli źle go czytam), myślę, że szukasz protokołu w celu C. Zobacz, na przykład, co to jest protokół? .
jww

Odpowiedzi:


633

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ą.


18
Myślę, że najbardziej odpowiednią częścią odpowiedzi było wspomnienie, że zamiast zdefiniować metody można użyć @protocol.
Chad Stewart

13
Wyjaśnienie: Możesz zadeklarować metody w @protocoldefinicji, ale nie możesz zdefiniować tam metod.
Richard

Dzięki metodzie kategorii w NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectordziała to bardzo dobrze.
Patrick Pijnappel

Działa to dla mnie, ponieważ IMHO, rzucając wyjątek, jest bardziej oczywiste dla innych programistów, że jest to pożądane zachowanie bardziej niż doesNotRecognizeSelector.
Chris

Za pomocą protokołów (na całkowicie abstrakcyjnej klasy) lub metoda szablonowa gdzie streszczenie klasa ma częściowe wdrożenie logiki / przepływu jak podane tutaj stackoverflow.com/questions/8146439/... . Zobacz moją odpowiedź poniżej.
hadaytullah

267

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.


5
@ Chuck, nie przegłosowałem, ale NSObjectodniesienie 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 :)
Dan Rosenstark,

Dlaczego nie chcesz po prostu używać protokołu w tym przypadku? Dla mnie to dobrze wiedzieć tylko dla kodów pośredniczących metod, ale nie dla całej klasy abstrakcyjnej. Przypadek użycia tego wydaje się ograniczony.
lewiguez

Nie głosowałem za tym, ale najbardziej prawdopodobną przyczyną jest sugestia, aby powołać wyjątek w metodzie init. Najpopularniejszy format podklasy uruchamia własną metodę init, wywołując self = [super init] - co posłusznie zgłasza wyjątek. Ten format działa dobrze dla większości metod, ale nigdy nie zrobiłbym tego w żadnym miejscu, w którym podklasa mogłaby nazwać to super implementacją.
Xono,

5
Absolutnie możesz tworzyć klasy abstrakcyjne w Objective-C i jest to dość powszechne. Istnieje wiele klas środowiska Apple, które to robią. Klasy abstrakcyjne Apple zgłaszają określone wyjątki (NSInvalidArgumentException), często wywołując NSInvalidAbstractInvocation (). Wywołanie metody abstrakcyjnej jest błędem programistycznym i dlatego zgłasza wyjątek. Fabryki abstrakcyjne są zwykle wdrażane jako klastry klas.
quellish

2
@ quellish: Jak powiedziałeś: wywołanie metody abstrakcyjnej jest błędem programistycznym. Powinno to być traktowane jako takie, zamiast polegać na raportowaniu błędów w czasie wykonywania (NSException). Wydaje mi się, że jest to największy problem dla programistów pochodzących z innych języków, w których abstrakcja oznacza „nie można utworzyć instancji tego typu obiektu” w Obj-C, co oznacza „coś pójdzie nie tak w czasie wykonywania, gdy utworzymy instancję tej klasy”.
Cross_

60

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 doesNotRecognizeSelectorwywołanie (patrz NSObjectdokumentacja).


1
Jasne, @TomA, mam nadzieję, że dostarczy ci pomysłów na inny kod, który możesz makrować. Moje najczęściej używane makro to proste odniesienie do singletonu: kod mówi, universe.thingale się rozwija [Universe universe].thing. Świetna zabawa, oszczędność tysięcy liter kodu ...
Dan Rosenstark

Świetny. Zmienił go trochę, ale: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm

1
@ Yar: Nie wydaje mi się. Używamy __PRETTY_FUNCTION__wszędzie, za pomocą makra DLog (...), jak sugerowano tutaj: stackoverflow.com/a/969291/38557 .
noamtm,

1
po więcej szczegółów -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk

1
jeśli dodasz klasę podstawową, a następnie odziedziczysz tę klasę na przykład 10 razy i zapomnisz zaimplementować to w jednej z klas, otrzymasz wiadomość z nazwą klasy podstawowej, która nie jest dziedziczona, tak jak w 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 implementeddziedziczoną z bazy HomeViewController - to poda więcej informacji
gbk

42

Rozwiązanie, które wymyśliłem to:

  1. Utwórz protokół dla wszystkiego, co chcesz w swojej klasie „abstrakcyjnej”
  2. Utwórz klasę podstawową (lub nazwij ją abstrakcyjną), która implementuje protokół. Dla wszystkich metod, które chcesz „abstrakcyjnie” zaimplementuj w pliku .m, ale nie w pliku .h.
  3. Niech twoja klasa potomna odziedziczy po klasie podstawowej ORAZ zaimplementuj protokół.

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.


2
+1 To jest naprawdę rozwiązanie najbardziej zbliżone do abstrakcyjnej klasy w Javie. Skorzystałem z tego podejścia i działa ono świetnie. Można nawet nazwać protokół tak samo, jak klasę podstawową (podobnie jak Apple 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.
codingFriend1

Ach, ale moja klasa abstrakcyjna implementuje część protokołu, reszta jest implementowana przez podklasy.
Roger CS Wernersson

1
możesz mieć tylko klasy potomne implementujące protokół i pozostawić wszystkie metody nadklasy razem zamiast pustych. następnie mają właściwości typu Superclass <MyProtocol>. Ponadto, dla dodatkowej elastyczności, możesz poprzedzić swoje metody w protokole znakiem @optional.
dotToString

Nie działa to w Xcode 5.0.2; generuje jedynie ostrzeżenie dla klasy „abstrakcyjnej”. Brak rozszerzenia klasy „abstrakcyjnej” powoduje wygenerowanie odpowiedniego ostrzeżenia kompilatora, ale oczywiście nie pozwala na dziedziczenie metod.
Topher Fangio

Wolę to rozwiązanie, ale naprawdę go nie lubię, tak naprawdę nie jest to dobra struktura kodu w projekcie. // powinien użyć tego jako typu podstawowego dla podklas. typedef BaseClass <BaseClassProtocol> BASECLASS; To tylko tydzień, nie podoba mi się to.
Itachi

35

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;
}

1
Nie myślałem o użyciu opcji -doesNotRecognizeSelector: i pod pewnym względem podoba mi się to podejście. Czy ktoś zna sposób, aby kompilator wydawał ostrzeżenie dla „abstrakcyjnej” metody utworzonej w ten sposób lub przez zgłoszenie wyjątku? To by było niesamowite ...
Quinn Taylor

26
Podejście doesNotRecognizeSelector zapobiega sugerowanemu przez Apple wzorowi self = [super init].
dlinsin

1
@david: Jestem pewien, że chodzi o jak najszybszy wyjątek. Idealnie powinno być w czasie kompilacji, ale ponieważ nie ma na to sposobu, zdecydowano się na wyjątek czasu wykonywania. Jest to podobne do niepowodzenia asercji, które nigdy nie powinno być podnoszone w kodzie produkcyjnym. W rzeczywistości twierdzenie (fałsz) może faktycznie być lepsze, ponieważ jest bardziej jasne, że ten kod nigdy nie powinien działać. Użytkownik nie może nic zrobić, aby to naprawić, programista musi to naprawić. Zatem zgłoszenie tutaj wyjątku lub niepowodzenia asercji brzmi jak dobry pomysł.
Rozsądny

2
Nie sądzę, że jest to realne rozwiązanie, ponieważ podklasy mogą mieć całkowicie uzasadnione wywołania do [super init].
Raffi Khatchadourian

@dlinsin: Właśnie tego chcesz, gdy klasa abstrakcyjna nie powinna być inicjowana przez init. Jego podklasy prawdopodobnie wiedzą, jaką super metodę wywołać, ale to zatrzymuje nieostrożne wywoływanie przez „nowe” lub „przydzielanie / inicjowanie”.
gnasher729,

21

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.


Wydaje mi się, że jest to odpowiedni sposób na zastosowanie przypadków abstrakcyjnych, przynajmniej wtedy, gdy ma to naprawdę oznaczać „interfejs” (tak jak w C ++). Czy są jakieś ukryte wady tego podejścia?
febeling

1
@febeling, klasy abstrakcyjne - przynajmniej w Javie - to nie tylko interfejsy. Definiują także niektóre (lub większość) zachowania. Takie podejście może być jednak dobre w niektórych przypadkach.
Dan Rosenstark,

Potrzebuję klasy bazowej do implementacji pewnej funkcjonalności, którą wszystkie podklasy współużytkują (usuwanie duplikacji), ale potrzebuję również protokołu dla innych metod, których klasa podstawowa nie powinna obsługiwać (część abstrakcyjna). Muszę więc użyć obu tych elementów, i to może być trudne, aby upewnić się, że twoje podklasy wdrażają się poprawnie.
LightningStryk

19

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 @dynamicwł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ć:

  1. Zaimplementuj każdą metodę abstrakcyjną w klasie bazowej.
  2. Zrób to wdrożenie [self doesNotRecognizeSelector:_cmd];
  3. … Po którym następuje __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”.
  4. Połącz kroki 2. i 3. w makrze lub dodaj adnotację, -[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.)

Dlaczego wybieram tę metodę?

  1. Sprawia, że ​​praca jest wykonywana sprawnie i nieco wygodnie.
  2. Jest dość łatwy do zrozumienia. (Okej, to __builtin_unreachable()może zaskoczyć ludzi, ale też łatwo to zrozumieć).
  3. Nie można go usunąć w kompilacjach wersji bez generowania innych ostrzeżeń kompilatora lub błędów - w przeciwieństwie do podejścia opartego na jednym z makr asercji.

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?


To moje nowe ulubione rozwiązanie - __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.
Alex MDC

12

Korzystanie @propertyi @dynamicmoż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 selectorpodczas 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.


7

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 @interfacemetce 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.


1
Nie mogłem tego zdobyć Złożyłem swoje sugestie na moim klasy bazowej -init metody i teraz Xcode nie pozwala mi utworzyć instancję klasy dziedziczonej a czas kompilacji błąd występuje brak ... . Czy mógłbyś wyjaśnić więcej?
anonim

Upewnij się, że masz -initmetodę w swojej podklasie.
Richard Stelling

2
Jest to to samo, co NS_UNAVAILABLE, który spowoduje błąd za każdym razem, gdy spróbujesz wywołać metodę oznaczoną takim atrybutem. Nie rozumiem, jak można go użyć w klasie abstrakcyjnej.
Ben Sinclair

7

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.

@protocol ProtocolName
// list of methods and properties
@end

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?


6

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


Czy byłoby po prostu zwrócić zero? Czy może to spowodować zgłoszenie wyjątku / innego błędu?
user2277872,

Cóż, to po to, żeby złapać programistów na bezpośrednie korzystanie z klasy, co, jak sądzę, stanowi dobre zastosowanie asercji.
bigkm

5

(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ć.

przykład podpowiedzi


4

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

3

Prawdopodobnie tego rodzaju sytuacje powinny się zdarzyć tylko w czasie programowania, więc może to działać:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

3

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.


2

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.


1

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.


Ale dostaję błąd, gdy wywołuję [super init] z klasy pochodnej tej klasy abstrakcyjnej. Jak to rozwiązać?
Sahil Doshi,

@SahilDoshi Przypuszczam, że to wada stosowania tej metody. Używam go, gdy nie chcę zezwalać na tworzenie instancji klasy i nic nie dziedziczy po tej klasie.
mahoukov

tak, zrozumiałem to. ale twoje rozwiązanie pomoże stworzyć coś takiego jak klasa singleton, w której nie chcemy, aby ktokolwiek dzwonił do init. zgodnie z moją wiedzą w przypadku klasy abstrakcyjnej niektóre klasy ją odziedziczą. w przeciwnym razie jaki jest pożytek z abstrakcyjnej klasy
Sahil Doshi,

0

Zmieniając nieco to, co sugeruje @redfood, stosując komentarz @ dotToString, faktycznie masz rozwiązanie przyjęte przez IGListKit na Instagramie .

  1. Utwórz protokół dla wszystkich metod, które nie mają sensu definiować w klasie bazowej (abstrakcyjnej), tzn. Wymagają one konkretnych implementacji u dzieci.
  2. Utwórz podstawową (abstrakcyjną) klasę, która nie implementuje tego protokołu. Możesz dodać do tej klasy wszelkie inne metody, które mają sens, aby mieć wspólną implementację.
  3. Wszędzie w twoim projekcie, jeśli dziecko z AbstractClassmusi być wprowadzane lub wyprowadzane jakąś metodą, wpisz je AbstractClass<Protocol>zamiast tego.

Ponieważ AbstractClassnie jest implementowany Protocol, jedynym sposobem na utworzenie AbstractClass<Protocol>instancji jest podklasowanie. Ponieważ AbstractClasssam 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ą, IGListSectionControllerktó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 IGListSectionControllerdo czegokolwiek przydatnego w ich ramach.


0

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

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

0

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

-3

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.