Il modo migliore per definire metodi privati ​​per una classe in Objective-C


355

Ho appena iniziato a programmare Objective-C e, avendo un background in Java, mi chiedo come le persone che scrivono i programmi Objective-C gestiscono i metodi privati.

Capisco che potrebbero esserci diverse convenzioni e abitudini e penso a questa domanda come un aggregatore delle migliori tecniche che le persone usano nel trattare con i metodi privati ​​in Objective-C.

Ti preghiamo di includere un argomento per il tuo approccio quando lo pubblichi. Perché va bene? Quali sono gli svantaggi (che conosci) e come li gestisci?


Per quanto riguarda le mie scoperte finora.

È possibile utilizzare le categorie [ad esempio MyClass (Private)] definite nel file MyClass.m per raggruppare metodi privati.

Questo approccio ha 2 problemi:

  1. Xcode (e compilatore?) Non controlla se si definiscono tutti i metodi nella categoria privata nel corrispondente blocco @implementation
  2. Devi inserire @interface che dichiara la tua categoria privata all'inizio del file MyClass.m, altrimenti Xcode si lamenta con un messaggio come "self potrebbe non rispondere al messaggio" privateFoo ".

Il primo numero può essere risolto con una categoria vuota [ad esempio MyClass ()].
Il secondo mi dà molto fastidio. Mi piacerebbe vedere i metodi privati ​​implementati (e definiti) verso la fine del file; Non so se sia possibile.


1
La gente potrebbe trovare questa domanda interessante: stackoverflow.com/questions/2158660/...
bbum

Risposte:


435

Come altri hanno già detto, non esiste un metodo privato in Objective-C. Tuttavia, a partire da Objective-C 2.0 (ovvero Mac OS X Leopard, iPhone OS 2.0 e versioni successive) è possibile creare una categoria con un nome vuoto (ovvero @interface MyClass ()) denominata Estensione di classe . La particolarità di un'estensione di classe è che le implementazioni del metodo devono andare @implementation MyClasscome i metodi pubblici. Quindi strutturo le mie lezioni in questo modo:

Nel file .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

E nel file .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

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

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

@end

Penso che il più grande vantaggio di questo approccio sia che ti consente di raggruppare le implementazioni dei tuoi metodi in base alla funzionalità, non alla distinzione pubblica / privata (a volte arbitraria).


8
e genererà una "MYClass potrebbe non rispondere a '-myPrivateMethod-", non un'eccezione / errore.
Özgür,

2
In realtà, questo sta iniziando a comparire nel codice della piastra di caldaia di Apple. ++
Chris Trahey,

75
con il compilatore LLVM 4 e successivi, non è nemmeno necessario farlo. puoi semplicemente definirli all'interno dell'implementazione senza doverli inserire in un'estensione di classe.
Abizern,

1
Se ricevi gli avvisi menzionati da @Comptrol, è perché hai definito un metodo di seguito piuttosto che sopra un altro metodo che lo chiama (vedi la risposta di Andy) - e ignori questi avvisi a tuo rischio e pericolo. Ho fatto questo errore e il compilatore ha confuso tutto bene fino a quando non ho annidato una chiamata come questa: if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Quindi fWidthCombined veniva sempre visualizzato come 0.
Wienke,

6
@Wienke Non è più necessario preoccuparsi dell'ordine. Le versioni recenti di LLVM troveranno il metodo anche se appare sotto dove viene chiamato.
Steve Waddicor,

52

Non esiste davvero un "metodo privato" in Objective-C, se il runtime può capire quale implementazione usarlo lo farà. Ma questo non vuol dire che non ci sono metodi che non fanno parte dell'interfaccia documentata. Per quei metodi penso che una categoria vada bene. Invece di mettere il file @interfacenella parte superiore del file .m come il punto 2, lo inserisco nel suo file .h. Una convenzione che seguo (e ho visto altrove, penso che sia una convenzione di Apple poiché Xcode ora fornisce il supporto automatico per esso) è quella di nominare un tale file dopo la sua classe e categoria con un + che li separa, quindi @interface GLObject (PrivateMethods)può essere trovato in GLObject+PrivateMethods.h. Il motivo per cui è stato fornito il file di intestazione è che è possibile importarlo nelle classi di unit test :-).

A proposito, per quanto riguarda l'implementazione / definizione dei metodi vicino alla fine del file .m, puoi farlo con una categoria implementando la categoria in fondo al file .m:

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

o con un'estensione di classe (la cosa che chiami una "categoria vuota"), definisci questi metodi per ultimi. I metodi Objective-C possono essere definiti e utilizzati in qualsiasi ordine nell'implementazione, quindi non c'è nulla che ti impedisca di mettere i metodi "privati" alla fine del file.

Anche con le estensioni di classe creerò spesso un'intestazione separata ( GLObject+Extension.h) in modo da poter usare quei metodi se necessario, imitando la visibilità "amico" o "protetta".

Da quando questa risposta è stata originariamente scritta, il compilatore clang ha iniziato a fare due passaggi per i metodi Objective-C. Ciò significa che puoi evitare di dichiarare completamente i tuoi metodi "privati", e che siano sopra o sotto il sito chiamante verranno trovati dal compilatore.


37

Anche se non sono un esperto di Objective-C, personalmente definisco solo il metodo nell'implementazione della mia classe. Certo, deve essere definito prima (sopra) di tutti i metodi che lo chiamano, ma richiede sicuramente la minima quantità di lavoro da fare.


4
Questa soluzione ha il vantaggio di evitare l'aggiunta di una struttura di programma superflua solo per evitare un avviso del compilatore.
Jan Hettich,

1
Anch'io tendo a farlo, ma non sono un esperto di Objective-C. Per gli esperti, c'è qualche motivo per non farlo in questo modo (a parte il problema di ordinazione del metodo)?
Eric Smith,

2
L'ordinamento dei metodi sembra essere un problema minore, ma se lo traduci in leggibilità del codice può diventare un problema piuttosto importante, specialmente quando lavori in gruppo.
borisdiakur,

17
L'ordinamento del metodo non è più significativo. Le versioni recenti di LLVM non si preoccupano per quale ordine vengano implementati i metodi. Quindi puoi adattarti all'ordine, senza dover prima dichiarare.
Steve Waddicor,

Vedi anche questa risposta da @justin
lagweezle

19

La definizione dei metodi privati ​​nel @implementationblocco è l'ideale per la maggior parte degli scopi. Clang li vedrà all'interno @implementation, indipendentemente dall'ordine di dichiarazione. Non è necessario dichiararli in una continuazione di classe (ovvero estensione di classe) o categoria denominata.

In alcuni casi, sarà necessario dichiarare il metodo nella continuazione della classe (ad es. Se si utilizza il selettore tra la continuazione della classe e il @implementation).

static le funzioni sono ottime per metodi privati ​​particolarmente sensibili o critici in termini di velocità.

Una convenzione per la denominazione dei prefissi può aiutarti a evitare la sostituzione accidentale di metodi privati ​​(trovo sicuro il nome della classe come prefisso).

Le categorie nominate (ad es. @interface MONObject (PrivateStuff)) Non sono una buona idea a causa di potenziali collisioni di denominazione durante il caricamento. Sono davvero utili solo per metodi amici o protetti (che raramente sono una buona scelta). Per essere avvisato dell'implementazione di categorie incomplete, è necessario implementarlo effettivamente:

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

Ecco un piccolo cheat sheet con annotazioni:

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

Un altro approccio che potrebbe non essere ovvio: un tipo C ++ può essere sia molto veloce che fornire un grado di controllo molto più elevato, riducendo al minimo il numero di metodi objc esportati e caricati.


1
+1 per l'utilizzo del nome completo della classe come prefisso del nome del metodo! È molto più sicuro di un semplice carattere di sottolineatura o persino del tuo TLA. (E se il metodo privato fosse in una libreria che usi in un altro dei tuoi progetti e dimentichi che hai già usato il nome, qualche anno o due fa ...?)
big_m

14

Potresti provare a definire una funzione statica sotto o sopra l'implementazione che porta un puntatore all'istanza. Sarà in grado di accedere a qualsiasi variabile dell'istanza.

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

1
Apple ha riservato i nomi con un carattere di sottolineatura principale per i propri usi.
Georg Schölly,

1
E se non usi i framework di Apple? Sviluppo spesso codice Objective-C senza i framework di Apple, infatti mi baso su Linux, Windows e Mac OS X. L'ho rimosso comunque considerando che la maggior parte delle persone che codificano in Objective-C probabilmente lo usano su Mac OS X.
dreamlax

3
Penso che questo sia un metodo veramente privato nel file .m. Altri metodi di categoria di classe non sono in realtà privati ​​perché non è possibile inserire privato per metodi nel blocco @interface ... @ end.
David.Chu.ca,

Perché dovresti farlo? se aggiungi semplicemente "-" all'inizio della definizione del metodo, accederai a "self" senza passare come parametro.
Guy

1
@Guy: perché quindi il metodo è rilevabile per riflessione, e quindi non è affatto privato.
dreamlax,

3

Potresti usare i blocchi?

@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

Sono consapevole che questa è una vecchia domanda, ma è una delle prime che ho trovato mentre cercavo una risposta a questa domanda. Non ho visto questa soluzione discussa altrove, quindi fatemi sapere se c'è qualcosa di sciocco nel farlo.


1
Quello che hai fatto qui è creare una variabile di tipo blocco globale, che non è davvero migliore di una funzione (e nemmeno veramente privata, dal momento che non è dichiarata static). Ma ho sperimentato l'assegnazione di blocchi agli ivar privati ​​(dal metodo init) - un po 'in stile JavaScript - che consente anche l'accesso agli ivar privati, cosa impossibile dalle funzioni statiche. Non sono ancora sicuro di quale preferisco.
big_m,

3

ogni oggetto nell'Obiettivo C è conforme al protocollo NSObject, che mantiene il metodo performSelector:. In precedenza stavo anche cercando un modo per creare alcuni metodi "di aiuto o privati" che non avevo bisogno di esporre a livello pubblico. Se vuoi creare un metodo privato senza sovraccarico e non devi definirlo nel tuo file di intestazione, dai un colpo ...

definire il metodo con una firma simile al codice seguente ...

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

quindi quando devi fare riferimento al metodo, chiamalo semplicemente come un selettore ...

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

questa riga di codice invocherà il metodo creato e non avrà un fastidioso avviso di non averlo definito nel file di intestazione.


6
In questo modo non hai modo di passare un terzo parametro.
Li Fumin,

2

Se volessi evitare il @interfaceblocco in alto, potresti sempre mettere le dichiarazioni private in un altro file MyClassPrivate.hnon ideale ma non ingombrare l'implementazione.

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

2

Un'altra cosa che non ho visto menzionato qui: Xcode supporta i file .h con "_private" nel nome. Supponiamo che tu abbia una classe MyClass - tu abbia MyClass.m e MyClass.h e ora puoi anche avere MyClass_private.h. Xcode lo riconoscerà e lo includerà nell'elenco di "Controparti" nell'editor Assistant.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"

1

Non c'è modo di aggirare il problema n. 2. Questo è solo il modo in cui funziona il compilatore C (e quindi il compilatore Objective-C). Se si utilizza l'editor XCode, il popup delle funzioni dovrebbe semplificare la navigazione nei blocchi @interfacee @implementationnel file.


1

C'è un vantaggio dell'assenza di metodi privati. Puoi spostare la logica che intendi nascondere nella classe separata e usarla come delegato. In questo caso puoi contrassegnare l'oggetto delegato come privato e non sarà visibile dall'esterno. Lo spostamento della logica nella classe separata (forse più) consente una migliore progettazione del progetto. Perché le tue classi diventano più semplici e i tuoi metodi sono raggruppati in classi con nomi propri.


0

Come altre persone hanno detto che definisce i metodi privati ​​in @implementation blocco è OK per la maggior parte degli scopi.

Sul tema dell'organizzazione del codice - Mi piace tenerli insieme pragma mark privateper facilitare la navigazione in Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.