objective c - tutorial - Exponer un método o propiedad privado de Objective-C a las subclases
objective c ultima version (7)
Eso es porque ni siquiera hay una distinción real entre privado y público. Si bien el compilador puede advertirte sobre una interfaz que no tiene un determinado método o variable de instancia, tu programa seguirá funcionando.
Según una charla oficial, una clase en Objective-C solo debería exponer los métodos y las propiedades públicas en su encabezado:
@interface MyClass : NSObject
@property (nonatomic, strong) MyPublicObject *publicObject;
- (void)publicMethod;
@end
y los métodos / propiedades privados deben mantenerse en la extensión de clase en el archivo .m:
@interface MyClass()
@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end
y no creo que haya un tipo protected
para cosas que son privadas pero accesibles desde subclases. Me pregunto, ¿hay alguna forma de lograr esto, además de declarar las propiedades / métodos privados públicamente?
Esto es posible utilizando una extensión de clase (no categoría) que incluya en los archivos de implementación tanto de la clase base como de las subclases.
Una extensión de clase se define de forma similar a una categoría, pero sin el nombre de la categoría:
@interface MyClass ()
En una extensión de clase, puede declarar propiedades, que podrán sintetizar los ivars de respaldo (XCode> 4.4 Síntesis automática de los ivars también funciona aquí).
En la clase de extensión, puede anular / refinar las propiedades (cambiar de solo lectura a readwrite, etc.) y agregar propiedades y métodos que serán "visibles" para los archivos de implementación (pero tenga en cuenta que las propiedades y métodos no son realmente privados y pueden todavía ser llamado por el selector).
Otros han propuesto usar un archivo de encabezado separado MyClass_protected.h para esto, pero esto también se puede hacer en el archivo de cabecera principal usando #ifdef
como este:
Ejemplo:
BaseClass.h
@interface BaseClass : NSObject
// foo is readonly for consumers of the class
@property (nonatomic, readonly) NSString *foo;
@end
#ifdef BaseClass_protected
// this is the class extension, where you define
// the "protected" properties and methods of the class
@interface BaseClass ()
// foo is now readwrite
@property (nonatomic, readwrite) NSString *foo;
// bar is visible to implementation of subclasses
@property (nonatomic, readwrite) int bar;
-(void)baz;
@end
#endif
BaseClass.m
// this will import BaseClass.h
// with BaseClass_protected defined,
// so it will also get the protected class extension
#define BaseClass_protected
#import "BaseClass.h"
@implementation BaseClass
-(void)baz {
self.foo = @"test";
self.bar = 123;
}
@end
ChildClass.h
// this will import BaseClass.h without the class extension
#import "BaseClass.h"
@interface ChildClass : BaseClass
-(void)test;
@end
ChildClass.m
// this will implicitly import BaseClass.h from ChildClass.h,
// with BaseClass_protected defined,
// so it will also get the protected class extension
#define BaseClass_protected
#import "ChildClass.h"
@implementation ChildClass
-(void)test {
self.foo = @"test";
self.bar = 123;
[self baz];
}
@end
Cuando llamas a #import
, básicamente copia y pega el archivo .h al lugar donde lo estás importando. Si tiene un #ifdef
, solo incluirá el código dentro si #define
con ese nombre.
En su archivo .h, no establece la definición para que las clases que importan .h no vean la extensión de clase protegida. En el archivo .m de clase base y subclase, use #define
antes de usar #import
para que el compilador incluya la extensión de clase protegida.
Mientras que las otras respuestas son correctas, me gustaría agregar ...
Privado, protegido y público están disponibles, por ejemplo, variables como tales:
@interface MyClass : NSObject {
@private
int varA;
@protected
int varB;
@public
int varC;
}
@end
Simplemente crea un archivo .h con tu extensión de clase. Importe esto en sus archivos .m. Por cierto, esta es una gran manera de probar miembros privados sin romper la encapsulación (no digo que debas probar métodos privados :)).
// MyClassProtectedMembers.h
@interface MyClass()
@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end
/////////////////
#import "MyClassProtectedMembers.h"
@implementation MyClass
// implement privateMethod here and any setters or getters with computed values
@end
Aquí hay una esencia de la idea: https://gist.github.com/philosopherdog/6461536b99ef73a5c32a
Su única opción es declararlo como público en el archivo de encabezado. Si quiere mantener al menos alguna separación de métodos, puede crear una categoría y tener allí todos sus métodos y atributos protegidos, pero al final todo seguirá siendo público.
#import "MyClass.h"
@interface MyClass (Protected)
- (void) protectedMethods;
@end
Una forma de resolver esto es volver a declarar la propiedad en la extensión de clase de su subclase, y luego agregar una declaración @dynamic
para que el compilador no cree una implementación principal de esa propiedad. Entonces algo así como:
@interface SuperClass ()
@property (nonatomic, strong) id someProperty;
@end
....
@interface SubClass ()
@property (nonatomic, strong) id someProperty;
@end
@implementation SubClass
@dynamic someProperty;
@end
Obviamente, esto no es ideal porque duplica una declaración privada visible. Pero es bastante conveniente y útil en algunas situaciones, por lo que podría evaluar, caso por caso, los peligros que conlleva esta duplicación frente a la exposición de la propiedad en la interfaz pública.
Una alternativa, que usa Apple en UIGestureRecognizer, es declarar la propiedad en un archivo de encabezado de categoría separado denominado explícitamente como "privado" o "protegido", por ejemplo, "SomeClass + Protected.h". De esta forma, otros programadores sabrán que no deberían importar el archivo. Pero, si no controlas el código del que estás heredando, esa no es una opción.
Veo buenas respuestas para hacer que las propiedades sean visibles, pero no veo exponer los métodos abordados muy claramente en ninguna de estas respuestas. Aquí es cómo he expuesto con éxito los métodos privados a la subclase utilizando una Categoría:
SomeSuperClass.m:
@implementation SomeSuperClass
-(void)somePrivateMethod:(NSString*)someArgument {
...
}
SomeChildClass.h
@interface SomeChildClass : SomeSuperClass
SomeChildClass.m
@interface SomeSuperClass (exposePrivateMethod)
-(void)somePrivateMethod:(NSString*)someArgument;
@end
@implementation SomeChildClass
-(void)doSomething {
[super somePrivateMethod:@"argument"];
}
@end