Najlepszy sposób na zdefiniowanie prywatnych metod dla klasy w Objective-C

355

Właśnie zacząłem programować Objective-C i mając doświadczenie w Javie, zastanawiam się, jak ludzie piszący programy Objective-C radzą sobie z metodami prywatnymi.

Rozumiem, że może istnieć kilka konwencji i przyzwyczajeń, i myślę o tym pytaniu jako agregat najlepszych technik, z których ludzie korzystają w kontaktach z prywatnymi metodami w Objective-C.

Podaj argument za podejściem podczas jego publikowania. Dlaczego to jest dobre? Jakie wady ma (o których wiesz) i jak sobie z nimi radzisz?


Co do moich dotychczasowych ustaleń.

Możliwe jest użycie kategorii [np. MyClass (Private)] zdefiniowanych w pliku MyClass.m do grupowania metod prywatnych.

Podejście to ma 2 problemy:

  1. Xcode (i kompilator?) Nie sprawdza, czy zdefiniujesz wszystkie metody w kategorii prywatnej w odpowiednim bloku @implementation
  2. Musisz umieścić @interface deklarujący swoją prywatną kategorię na początku pliku MyClass.m, w przeciwnym razie Xcode narzeka na komunikat typu „ja może nie odpowiedzieć na wiadomość„ privateFoo ”.

Pierwszy problem można obejść z pustą kategorią [np. MyClass ()].
Drugi bardzo mnie niepokoi. Chciałbym, aby prywatne metody zostały zaimplementowane (i zdefiniowane) na końcu pliku; Nie wiem czy to możliwe.

Yurii Soldak
źródło
1
Ludzie mogą uznać to pytanie za interesujące: stackoverflow.com/questions/2158660/…
bbum
4
Dlaczego nie pominąć deklaracji metody prywatnej ?
ma11hew28,

Odpowiedzi:

436

Jak już powiedzieli inni, nie ma czegoś takiego jak prywatna metoda w Objective-C. Jednak począwszy od Objective-C 2.0 (czyli Mac OS X Leopard, iPhone OS 2.0 i nowsze) możesz utworzyć kategorię o pustej nazwie (tj. @interface MyClass ()) O nazwie Class Extension . Unikalne w rozszerzeniu klasy jest to, że implementacje metod muszą iść tak samo @implementation MyClassjak metody publiczne. Więc układam moje klasy w ten sposób:

W pliku .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

I w pliku .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

Myślę, że największą zaletą tego podejścia jest to, że pozwala on pogrupować implementacje metod według funkcjonalności, a nie (czasem arbitralnego) publicznego / prywatnego rozróżnienia.

Alex
źródło
8
i wygeneruje „MYClass może nie odpowiadać na„ -myPrivateMethod- ”, a nie wyjątek / błąd.
Özgür
2
To faktycznie zaczyna pojawiać się w kodzie typu Boiler firmy Apple. ++
Chris Trahey,
75
dzięki kompilatorowi LLVM 4 i nowszym nie musisz tego robić. możesz po prostu zdefiniować je w swojej implementacji bez konieczności umieszczania ich w rozszerzeniu klasy.
Abizern
1
Jeśli otrzymasz ostrzeżenia, o których wspomina @Ctrtrol, to dlatego, że zdefiniowałeś metodę poniżej, a nie inną, która ją wywołuje (patrz odpowiedź Andy'ego) - i ignorujesz te ostrzeżenia na własne ryzyko. Popełniłem ten błąd i kompilator if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...zaczął się mętnieć, dopóki nie zagnieździłam takiego wywołania: wtedy fWidthCombined zawsze pojawiał się jako 0.
Wienke
6
@Wienke Nie musisz się już martwić o zamówienie. Najnowsze wersje LLVM znajdą tę metodę, nawet jeśli pojawi się poniżej miejsca jej wywołania.
Steve Waddicor
52

W Objective-C nie ma tak naprawdę „prywatnej metody”, jeśli środowisko wykonawcze może ustalić, która implementacja go wykorzysta. Ale to nie znaczy, że nie ma metod, które nie są częścią udokumentowanego interfejsu. W przypadku tych metod uważam, że kategoria jest w porządku. Zamiast umieszczać @interfacena początku pliku .m, jak w punkcie 2, umieściłem go we własnym pliku .h. Konwencja, którą stosuję (i widziałem gdzie indziej, myślę, że jest to konwencja Apple, ponieważ Xcode zapewnia teraz automatyczne wsparcie dla niej), polega na nazwaniu takiego pliku po klasie i kategorii za pomocą + oddzielającego go, więc @interface GLObject (PrivateMethods)można go znaleźć w GLObject+PrivateMethods.h. Powodem udostępnienia pliku nagłówkowego jest to, że możesz go zaimportować do swoich klas testów jednostkowych :-).

Nawiasem mówiąc, jeśli chodzi o wdrażanie / definiowanie metod pod koniec pliku .m, możesz to zrobić za pomocą kategorii, implementując kategorię na dole pliku .m:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

lub z rozszerzeniem klasy (rzecz, którą nazywacie „pustą kategorią”), wystarczy zdefiniować te metody na końcu. Metody Objective-C można definiować i stosować w dowolnej kolejności w implementacji, więc nic nie stoi na przeszkodzie, aby umieścić metody „prywatne” na końcu pliku.

Nawet z rozszerzeniami klas często tworzę osobny nagłówek ( GLObject+Extension.h), aby w razie potrzeby móc korzystać z tych metod, naśladując widoczność „przyjaciela” lub „chronionej”.

Ponieważ ta odpowiedź została napisana pierwotnie, kompilator clang zaczął wykonywać dwa przejścia dla metod Objective-C. Oznacza to, że możesz uniknąć całkowitego zadeklarowania swoich „prywatnych” metod i czy są one powyżej czy poniżej strony wywołującej, zostaną one znalezione przez kompilator.


źródło
37

Chociaż nie jestem ekspertem od Objective-C, osobiście po prostu definiuję metodę w implementacji mojej klasy. To prawda, że ​​należy go zdefiniować przed (powyżej) dowolnymi metodami go wywołującymi, ale zdecydowanie zajmuje to najmniej pracy.

Andy
źródło
4
Rozwiązanie to ma tę zaletę, że pozwala uniknąć dodawania zbędnej struktury programu, aby uniknąć ostrzeżenia kompilatora.
Jan Hettich,
1
Ja też to zwykle robię, ale nie jestem też ekspertem z Objective-C. Czy dla ekspertów jest jakiś powód, aby nie robić tego w ten sposób (oprócz kwestii zamawiania metod)?
Eric Smith
2
Kolejność metod wydaje się drobnym problemem, ale jeśli przełożysz ją na czytelność kodu , może stać się dość istotnym problemem, szczególnie podczas pracy w zespole.
borisdiakur
17
Kolejność metod nie ma już znaczenia. Najnowsze wersje LLVM nie dbają o kolejność implementowanych metod. Możesz więc dopasować się do zamówienia bez konieczności wcześniejszego zgłoszenia.
Steve Waddicor
Zobacz także tę odpowiedź od @justin
lagweezle
19

Zdefiniowanie prywatnych metod w @implementationbloku jest idealne do większości celów. Clang zobaczy je w @implementation, niezależnie od kolejności deklaracji. Nie ma potrzeby deklarowania ich w kontynuacji klasy (aka rozszerzenie klasy) lub kategorii nazwanej.

W niektórych przypadkach będziesz musiał zadeklarować metodę w kontynuacji klasy (np. Jeśli używasz selektora między kontynuacją klasy a @implementation).

static funkcje są bardzo dobre dla szczególnie wrażliwych lub krytycznych pod względem szybkości prywatnych metod.

Konwencja nazywania prefiksów może pomóc ci uniknąć przypadkowego zastąpienia metod prywatnych (uważam, że nazwa klasy jest bezpieczna dla prefiksu).

Nazwane kategorie (np. @interface MONObject (PrivateStuff)) Nie są szczególnie dobrym pomysłem ze względu na potencjalne kolizje nazw podczas ładowania. Są one naprawdę przydatne tylko w przypadku metod przyjacielskich lub chronionych (które bardzo rzadko są dobrym wyborem). Aby upewnić się, że jesteś ostrzegany przed niekompletnymi implementacjami kategorii, powinieneś to zaimplementować:

@implementation MONObject (PrivateStuff)
...HERE...
@end

Oto mała ściągawka z adnotacjami:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Inne podejście, które może nie być oczywiste: typ C ++ może być zarówno bardzo szybki, jak i zapewniać znacznie wyższy stopień kontroli, minimalizując jednocześnie liczbę eksportowanych i ładowanych metod objc.

justin
źródło
1
+1 za użycie pełnej nazwy klasy jako prefiksu nazwy metody! Jest o wiele bezpieczniejszy niż tylko podkreślenie lub nawet własna TLA. (Co jeśli prywatna metoda znajduje się w bibliotece, której używasz w innym ze swoich projektów i zapominasz, że już
używałeś
14

Możesz spróbować zdefiniować funkcję statyczną poniżej lub powyżej implementacji, która przenosi wskaźnik do instancji. Będzie mógł uzyskać dostęp do dowolnej zmiennej Twojego wystąpienia.

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end
dreamlax
źródło
1
Nazwy zastrzeżone Apple z wiodącym podkreśleniem na własne potrzeby.
Georg Schölly,
1
A co, jeśli nie korzystasz z platform Apple? Często opracowuję kod Objective-C bez frameworków Apple, w rzeczywistości buduję na Linux, Windows i Mac OS X. I tak go usunąłem, biorąc pod uwagę, że większość osób, które kodują w Objective-C, prawdopodobnie używa go w Mac OS X.
dreamlax
3
Myślę, że to naprawdę prywatna metoda w pliku .m. Inne metody kategorii klas w rzeczywistości nie są prywatne, ponieważ po prostu nie można ustawić prywatnych metod w interfejsie @interface ... @ end.
David.Chu.ca
Dlaczego chcesz to zrobić? jeśli po prostu dodasz „-” na początku definicji metody, będziesz mieć dostęp do „ja” bez podawania jako parametru.
Guy
1
@Guy: ponieważ wtedy metoda jest wykrywalna przez odbicie, a zatem w ogóle nie jest prywatna.
dreamlax
3

Możesz użyć bloków?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

Wiem, że to stare pytanie, ale jedno z pierwszych, jakie znalazłem, kiedy szukałem odpowiedzi na to właśnie pytanie. Nigdzie indziej nie widziałem tego rozwiązania, więc daj mi znać, jeśli jest w tym coś głupiego.

FellowMD
źródło
1
To, co zrobiłeś tutaj, to utworzenie globalnej zmiennej typu blokowego, która nie jest tak naprawdę lepsza niż funkcja (a nawet nie jest naprawdę prywatna, ponieważ nie jest zadeklarowana static). Ale eksperymentowałem z przypisywaniem bloków prywatnym ivarom (z metody init) - w pewnym sensie w stylu JavaScript - który pozwala również na dostęp do prywatnych ivars, co jest niemożliwe z funkcji statycznych. Nie jestem pewien, który wolę.
big_m,
3

wszystkie obiekty w Celu C są zgodne z protokołem NSObject, który utrzymuje performSelector: metodęWcześniej szukałem też sposobu na stworzenie „pomocniczych lub prywatnych” metod, których nie potrzebowałem ujawniać na poziomie publicznym. Jeśli chcesz stworzyć prywatną metodę bez narzutów i nie musisz jej definiować w pliku nagłówkowym, spróbuj tego ...

zdefiniuj swoją metodę z podobną sygnaturą jak poniższy kod ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

wtedy, gdy musisz odwołać się do metody, po prostu wywołaj ją jako selektor ...

[self performSelector:@selector(myHelperMethod:)];

ten wiersz kodu wywoła metodę, którą utworzyłeś i nie będzie irytujący ostrzeżenie o braku zdefiniowania jej w pliku nagłówkowym.

Zack Sheppard
źródło
6
W ten sposób nie można przekazać trzeciego parametru.
Li Fumin
2

Jeśli chcesz uniknąć @interfacebloku na górze, zawsze możesz umieścić prywatne deklaracje w innym pliku, co MyClassPrivate.hnie jest idealne, ale nie zaśmieca implementacji.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end
rebelzach
źródło
2

Jeszcze jedna rzecz, o której tu nie wspominałem - Xcode obsługuje pliki .h z nazwą „_private” w nazwie. Załóżmy, że masz klasę MyClass - masz MyClass.m i MyClass.h, a teraz możesz także mieć MyClass_private.h. Xcode rozpozna to i umieści ją na liście „odpowiedników” w edytorze asystenta.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schonthal
źródło
1

Nie ma sposobu na obejście problemu nr 2. Tak właśnie działa kompilator C (a zatem i kompilator Objective-C). Jeśli korzystasz z edytora XCode, wyskakujące okienko funkcji powinno ułatwić nawigację w blokach @interfacei @implementationw pliku.

Barry Wark
źródło
1

Zaletą nieobecności metod prywatnych. Możesz przenieść logikę, którą chciałeś ukryć, do osobnej klasy i użyć jej jako delegata. W takim przypadku możesz oznaczyć obiekt delegowany jako prywatny i nie będzie on widoczny z zewnątrz. Przeniesienie logiki do osobnej klasy (może kilku) usprawnia projektowanie twojego projektu. Ponieważ twoje klasy stają się prostsze, a twoje metody są pogrupowane w klasy o odpowiednich nazwach.

Sneg
źródło
0

Jak powiedzieli inni, zdefiniowanie prywatnych metod w @implementationbloku jest w większości przypadków OK.

Na temat organizacji kodu - lubię trzymać je razem, pragma mark privateaby ułatwić nawigację w Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Mediolan
źródło