La mejor manera de definir métodos privados para una clase en Objective-C

355

Acabo de comenzar a programar Objective-C y, teniendo experiencia en Java, me pregunto cómo las personas que escriben programas Objective-C manejan métodos privados.

Entiendo que puede haber varias convenciones y hábitos y pienso en esta pregunta como un agregador de las mejores técnicas que las personas usan para tratar con métodos privados en Objective-C.

Incluya un argumento para su enfoque cuando lo publique. ¿Por qué es bueno? ¿Qué inconvenientes tiene (que usted sepa) y cómo lidiar con ellos?


En cuanto a mis hallazgos hasta ahora.

Es posible utilizar categorías [por ejemplo, MyClass (Private)] definidas en el archivo MyClass.m para agrupar métodos privados.

Este enfoque tiene 2 problemas:

  1. Xcode (¿y el compilador?) No comprueba si define todos los métodos en la categoría privada en el bloque @implementation correspondiente
  2. Debe poner @interface declarando su categoría privada al comienzo del archivo MyClass.m, de lo contrario, Xcode se queja con un mensaje como "self puede no responder al mensaje" privateFoo ".

El primer problema se puede solucionar con una categoría vacía [por ejemplo, MyClass ()].
El segundo me molesta mucho. Me gustaría ver métodos privados implementados (y definidos) cerca del final del archivo; No sé si eso es posible.

Yurii Soldak
fuente
1
La gente puede encontrar esta pregunta interesante: stackoverflow.com/questions/2158660/…
bbum

Respuestas:

435

No hay, como ya han dicho otros, un método privado en Objective-C. Sin embargo, a partir de Objective-C 2.0 (que significa Mac OS X Leopard, iPhone OS 2.0 y posterior) puede crear una categoría con un nombre vacío (es decir, @interface MyClass ()llamado Extensión de clase) . Lo único de una extensión de clase es que las implementaciones de los métodos deben ir de la misma manera @implementation MyClassque los métodos públicos. Entonces estructuro mis clases así:

En el archivo .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Y en el archivo .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

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

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

@end

Creo que la mayor ventaja de este enfoque es que le permite agrupar las implementaciones de sus métodos por funcionalidad, no por la distinción público / privado (a veces arbitrario).

Alex
fuente
8
y generará un "MYClass puede no responder a '-myPrivateMethod-", no una excepción / error.
Özgür
2
Esto en realidad está comenzando a aparecer en el código repetitivo de Apple. ++
Chris Trahey
75
con el compilador LLVM 4 y posteriores, ni siquiera necesita hacer esto. solo puede definirlos dentro de su implementación sin necesidad de colocarlos en una extensión de clase.
Abizern
1
Si recibe las advertencias que @Comptrol menciona, es porque ha definido un método a continuación en lugar de otro que lo llama (vea la respuesta de Andy), e ignora estas advertencias bajo su propio riesgo. if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Cometí este error y el compilador salió bien hasta que anidé una llamada como esta: Entonces fWidthCombined siempre aparecía como 0.
Wienke
66
@Wienke Ya no es necesario preocuparse por el pedido. Las versiones recientes de LLVM encontrarán el método incluso si aparece debajo de donde se llama.
Steve Waddicor
52

Realmente no existe un "método privado" en Objective-C, si el tiempo de ejecución puede determinar qué implementación usar lo hará. Pero eso no quiere decir que no haya métodos que no sean parte de la interfaz documentada. Para esos métodos, creo que una categoría está bien. En lugar de poner el @interfaceen la parte superior del archivo .m como su punto 2, lo pondría en su propio archivo .h. Una convención que sigo (y he visto en otros lugares, creo que es una convención de Apple, ya que Xcode ahora brinda soporte automático) es nombrar dicho archivo después de su clase y categoría con un + separándolos, por lo que @interface GLObject (PrivateMethods)se puede encontrar en él GLObject+PrivateMethods.h. La razón para proporcionar el archivo de encabezado es para que pueda importarlo en sus clases de prueba de unidad :-).

Por cierto, en lo que respecta a la implementación / definición de métodos cerca del final del archivo .m, puede hacerlo con una categoría implementando la categoría en la parte inferior del archivo .m:

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

o con una extensión de clase (lo que llama una "categoría vacía"), simplemente defina esos métodos al final. Los métodos Objective-C se pueden definir y utilizar en cualquier orden en la implementación, por lo que no hay nada que le impida colocar los métodos "privados" al final del archivo.

Incluso con las extensiones de clase, a menudo crearé un encabezado separado ( GLObject+Extension.h) para poder usar esos métodos si es necesario, imitando la visibilidad de "amigo" o "protegido".

Desde que esta respuesta se escribió originalmente, el compilador clang ha comenzado a hacer dos pases para los métodos Objective-C. Esto significa que puede evitar declarar completamente sus métodos "privados", y si el compilador los encontrará por encima o por debajo del sitio de llamada.


fuente
37

Si bien no soy un experto en Objective-C, personalmente solo defino el método en la implementación de mi clase. Por supuesto, debe definirse antes (arriba) de cualquier método que lo llame, pero definitivamente requiere la menor cantidad de trabajo.

Andy
fuente
44
Esta solución tiene la ventaja de que evita agregar una estructura de programa superflua solo para evitar una advertencia del compilador.
Jan Hettich
1
También tiendo a hacer esto, pero tampoco soy un experto en Objective-C. Para los expertos, ¿hay alguna razón para no hacerlo de esta manera (aparte del problema de ordenar el método)?
Eric Smith
2
La ordenación de métodos parece ser un problema menor, pero si lo traduce a legibilidad de código , puede convertirse en un problema bastante importante, especialmente cuando se trabaja en equipo.
borisdiakur
17
La ordenación de métodos ya no es significativa. Las versiones recientes de LLVM no les importa en qué orden se implementan los métodos. Para que pueda adaptarse al pedido, sin necesidad de declarar primero.
Steve Waddicor
Ver también esta respuesta de @justin
lagweezle
19

Definir sus métodos privados en el @implementationbloque es ideal para la mayoría de los propósitos. Clang los verá dentro del @implementation, independientemente del orden de la declaración. No es necesario declararlos en una continuación de clase (también conocida como extensión de clase) o categoría con nombre.

En algunos casos, deberá declarar el método en la continuación de la clase (por ejemplo, si usa el selector entre la continuación de la clase y el @implementation).

static Las funciones son muy buenas para métodos privados particularmente sensibles o críticos para la velocidad.

Una convención para nombrar prefijos puede ayudarlo a evitar anular accidentalmente métodos privados (encuentro el nombre de la clase como un prefijo seguro).

Las categorías con nombre (por ejemplo @interface MONObject (PrivateStuff)) no son una idea particularmente buena debido a posibles colisiones de nombres al cargar. En realidad, solo son útiles para métodos protegidos o amigos (que rara vez son una buena opción). Para asegurarse de que se le advierta sobre implementaciones de categoría incompletas, en realidad debería implementarlo:

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

Aquí hay una pequeña hoja de trucos anotada:

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

Otro enfoque que puede no ser obvio: un tipo C ++ puede ser muy rápido y proporcionar un grado de control mucho mayor, al tiempo que minimiza el número de métodos objc exportados y cargados.

justin
fuente
1
¡+1 por usar el nombre completo de la clase como prefijo del nombre del método! Es mucho más seguro que solo un guión bajo o incluso su propio TLA. (¿Qué sucede si el método privado está en una biblioteca que usa en otro de sus proyectos y olvida que ya usó el nombre, hace algún año o dos ...?)
big_m
14

Podría intentar definir una función estática debajo o encima de su implementación que tome un puntero a su instancia. Podrá acceder a cualquiera de sus variables de instancias.

//.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
fuente
1
Apple reservó nombres con un guión bajo para sus propios usos.
Georg Schölly
1
¿Y si no usas los frameworks de Apple? Frecuentemente desarrollo código Objective-C sin los marcos de Apple, de hecho construyo en Linux, Windows y Mac OS X. Lo eliminé de todos modos teniendo en cuenta que la mayoría de las personas que codifican en Objective-C probablemente lo usan en Mac OS X.
dreamlax
3
Creo que este es un método verdaderamente privado en el archivo .m. Otros métodos de categoría de clase en realidad no son privados porque simplemente no puede poner métodos privados para @interface ... @ end block.
David.Chu.ca
¿Por qué harías eso? si simplemente agrega "-" al comienzo de la definición del método, tendrá que acceder a "self" sin pasar como parámetro.
Guy
1
@Guy: porque entonces el método es detectable por reflexión y, por lo tanto, no es privado en absoluto.
dreamlax
3

¿Podrías usar bloques?

@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

Soy consciente de que esta es una vieja pregunta, pero es una de las primeras que encontré cuando estaba buscando una respuesta a esta misma pregunta. No he visto esta solución discutida en ningún otro lado, así que avíseme si hay algo tonto al hacer esto.

FellowMD
fuente
1
Lo que ha hecho aquí es crear una variable global de tipo bloque, que no es realmente mejor que una función (y ni siquiera es realmente privada, ya que no se declara static). Pero he estado experimentando con la asignación de bloques a ivars privados (desde el método init), un poco estilo JavaScript, que también permite el acceso a ivars privados, algo que no es posible desde las funciones estáticas. Aún no estoy seguro de cuál prefiero.
big_m
3

Todos los objetos en el Objetivo C se ajustan al protocolo NSObject, que se mantiene en el método performSelector . También estaba buscando una forma de crear algunos métodos "auxiliares o privados" que no necesitaba exponer a nivel público. Si desea crear un método privado sin gastos generales y sin tener que definirlo en su archivo de encabezado, intente esto ...

defina su método con una firma similar al código siguiente ...

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

luego, cuando necesite hacer referencia al método, simplemente llámelo como selector ...

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

esta línea de código invocará el método que creó y no tendrá una advertencia molesta sobre no tenerlo definido en el archivo de encabezado.

Zack Sheppard
fuente
66
De esta manera no tiene forma de pasar un tercer parámetro.
Li Fumin
2

Si desea evitar el @interfacebloqueo en la parte superior, siempre puede colocar las declaraciones privadas en otro archivo que MyClassPrivate.hno es ideal pero que no complica la implementación.

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
revs rebelzach
fuente
2

Una cosa más que no he visto aquí: Xcode admite archivos .h con "_private" en el nombre. Digamos que tiene una clase MyClass: tiene MyClass.m y MyClass.h y ahora también puede tener MyClass_private.h. Xcode lo reconocerá y lo incluirá en la lista de "Contrapartes" en el Editor Asistente.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schonthal
fuente
1

No hay forma de evitar el problema # 2. Así es como funciona el compilador de C (y, por lo tanto, el compilador de Objective-C). Si usa el editor XCode, la ventana emergente de funciones debería facilitar la navegación por los bloques @interfacey @implementationen el archivo.

Barry Wark
fuente
1

Hay un beneficio de la ausencia de métodos privados. Puede mover la lógica que pretendía ocultar a la clase separada y usarla como delegado. En este caso, puede marcar el objeto delegado como privado y no será visible desde el exterior. Mover la lógica a la clase separada (quizás varias) hace un mejor diseño de su proyecto. Porque tus clases se vuelven más simples y tus métodos se agrupan en clases con nombres propios.

Sneg
fuente
0

Como otras personas dijeron, definir métodos privados en el @implementationbloque está bien para la mayoría de los propósitos.

Sobre el tema de la organización del código : me gusta mantenerlos juntos pragma mark privatepara facilitar la navegación en Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Milán
fuente