16

Me gustaría especificar un protocolo Objective-C con una rutina opcional. Cuando la rutina no es implementada por una clase conforme al protocolo, me gustaría usar una implementación predeterminada en su lugar. ¿Hay algún lugar en el protocolo en el que pueda definir esta implementación predeterminada? De lo contrario, ¿cuál es la mejor práctica para reducir la copia y el pegado de esta implementación predeterminada en todo el lugar?¿Cómo proporciono una implementación predeterminada para un protocolo Objective-C?

+2

Este post ofrece una novedosa solución elegante,: utilizar una categoría en NSObject. http://stackoverflow.com/questions/19329309/whats-wrong-with-using-a-category-on-nsobject-to-provide-a-default-protocol-imp?rq=1 –

Respuesta

13

Los protocolos de Objective-C no son asequibles para las implementaciones predeterminadas. Son puramente colecciones de declaraciones de métodos que pueden ser implementadas por otras clases. La práctica estándar en Objective-C es probar un objeto en tiempo de ejecución para ver si responde al selector dado antes de llamar a ese método, usando - [NSObject respondeSSelector:]. Si el objeto e no responde al selector dado, el método no se llama.

Una forma de lograr el resultado que está buscando sería definir un método que encapsule el comportamiento predeterminado que está buscando en la clase de llamada, y llamar a ese método si el objeto no pasa la prueba.

Otro enfoque sería hacer que el método sea requerido en el protocolo, y proporcionar implementaciones predeterminadas en las superclases de cualquier clase en las que no desee proporcionar una implementación específica.

Probablemente existan otras opciones también, pero en general no hay una práctica estándar particular en Objective-C, excepto tal vez simplemente no llamar al método dado si el objeto no lo ha implementado, por mi primer párrafo, arriba.

4

Una forma realmente fascinante es utilizar el tiempo de ejecución. En la puesta en marcha, muy temprano en la ejecución del programa, haga lo siguiente:

  1. enumerar todas las clases, encontrar las clases que implementan el protocolo
  2. Comprobar si la clase implementa un método
  3. Si no es así, agregar a la clase la implementación predeterminada

Se puede lograr sin muchos problemas.

+1

¿Cómo te atreves a llamar a esto hackish ;) –

+5

De hecho, he escrito un módulo completo de dominio público para hacer esto. No es tan hackiano como suena, ya que utiliza una funcionalidad completamente compatible y documentada, y ha sido probado exhaustivamente en el código de producción. http://code.google.com/p/libextobjc/source/browse/extobjc/Modules/EXTConcreteProtocol.h –

+0

OK sustituyo "hacker" con "fascinante". ¿Te satisfará eso a todos? – Yuji

14

No existe una forma estándar para hacerlo, ya que los protocolos no deberían definir ninguna implementación.

Dado que Objective-C viene con un tiempo de ejecución limpio, puedes por supuesto agregar un comportamiento si realmente crees que debes hacerlo de esa manera (y no hay posibilidad de lograr lo mismo con la herencia).

Diga usted declaró MyProtocol, a continuación, sólo tiene que añadir una interfaz con el mismo nombre en el archivo .h debajo de su declaración de protocolo:

@interface MyProtocol : NSObject <MyProtocol> 

+ (void)addDefaultImplementationForClass:(Class)conformingClass; 

@end 

y crear un archivo de aplicación correspondiente (usando MAObjCRuntime para facilitar la lectura aquí, pero el funciones de tiempo de ejecución estándar no serían mucho más código):

@implementation MyProtocol 

+ (void)addDefaultImplementationForClass:(Class)conformingClass { 
    RTProtocol *protocol = [RTProtocol protocolWithName:@"MyProtocol"]; 
    // get all optional instance methods 
    NSArray *optionalMethods = [protocol methodsRequired:NO instance:YES]; 
    for (RTMethod *method in optionalMethods) { 
    if (![conformingClass rt_methodForSelector:[method selector]]) { 
     RTMethod *myMethod = [self rt_methodForSelector:[method selector]]; 
     // add the default implementation from this class 
     [conformingClass rt_addMethod:myMethod]; 
    } 
    } 
} 

- (void)someOptionalProtocolMethod { 
    // default implementation 
    // will be added to any class that calls addDefault...: on itself 
} 

a continuación, sólo tiene que llamar

[MyProtocol addDefaultImplementationForClass:[self class]]; 

en el inicializador de su clase conforme al protocolo y se agregarán todos los métodos predeterminados.

+0

En un momento probé con https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTConcreteProtocol.h pero no pude hacerlo funcionar, y la parte de protocolos concretos parecía no haber sido mantenida. Entonces eso me hizo dudar si iba en la dirección correcta. Pero veré MAObjCRuntime o las funciones de tiempo de ejecución estándar. Gracias por el puntero! – Grav

+0

He proporcionado una implementación a continuación que no utiliza bibliotecas de terceros. ver http://stackoverflow.com/questions/4330656/how-do-i-provide-a-default-implementation-for-an-objective-c-protocol/23066691#23066691 –

1

Como Ryan mencionó que no hay implementaciones predeterminadas para los protocolos, otra opción para implementar en la superclase sería implementar un tipo de clase "Manejador" que se puede incluir en cualquier clase que quiera proporcionar la implementación predeterminada, El método apropiado llama a la implementación predeterminada de los manejadores.

1

Terminé creando una macro que tiene una implementación predeterminada del método.

Lo he definido en el archivo de encabezado del protocolo, y luego es solo una línea en cada implementación.

De esta manera, no tengo que cambiar la implementación en varios lugares, y se hace en tiempo de compilación, por lo que no es necesaria la magia en tiempo de ejecución.

+1

Una macro ?! boo, siseo La mejor respuesta es de "w.m." Debe crear una clase que contenga todas las implementaciones predeterminadas. También proporcione una utilidad (como addDefaultImplementationForClass) que copie los métodos predeterminados (utilizando class_addMethod). Llama a la utilidad en cada clase para inicializar. No hay nada de hackish en esto cuando se considera que Objective-C es un descendiente de LISP. –

+0

Acepto que las macros no son una buena solución, y que aprovechar el tiempo de ejecución es idiomático ObjC. De hecho, ya abandoné las macros, y actualmente estoy usando un objeto auxiliar que dicta mi protocolo. Pero eso va en contra del principio de menor conocimiento, así que creo que consideraré la solución de w.m. – Grav

1

Estoy de acuerdo con "W. M." Una solución muy buena es poner todas las implementaciones predeterminadas en una interfaz (con el mismo nombre que el protocolo). En el método "+ initialize" de cualquier subclase, simplemente puede copiar cualquier método no implementado desde la interfaz predeterminada en sí mismo.

Las siguientes funciones de ayuda trabajaron para mí

#import <objc/runtime.h> 

// Get the type string of a method, such as "[email protected]:". 
// Caller must allocate sufficent space. Result is null terminated. 
void getMethodTypes(Method method, char*result, int maxResultLen) 
{ 
    method_getReturnType(method, result, maxResultLen - 1); 
    int na = method_getNumberOfArguments(method); 
    for (int i = 0; i < na; ++i) 
    { 
     unsigned long x = strlen(result); 
     method_getArgumentType(method, i, result + x, maxResultLen - 1 - x); 
    } 
} 

// This copies all the instance methods from one class to another 
// that are not already defined in the destination class. 
void copyMissingMethods(Class fromClass, Class toClass) 
{ 
    // This gets the INSTANCE methods only 
    unsigned int numMethods; 
    Method* methodList = class_copyMethodList(fromClass, &numMethods); 
    for (int i = 0; i < numMethods; ++i) 
    { 
     Method method = methodList[i]; 
     SEL selector = method_getName(method); 
     char methodTypes[50]; 
     getMethodTypes(method, methodTypes, sizeof methodTypes); 

     if (![toClass respondsToSelector:selector]) 
     { 
      IMP methodImplementation = class_getMethodImplementation(fromClass, selector); 
      class_addMethod(toClass, selector, methodImplementation, methodTypes); 
     } 
    } 
    free(methodList); 
} 

Posteriormente, se llama en su inicializador de la clase como ...

@interface Foobar : NSObject<MyProtocol> 
@end 

@implementation Foobar 
+(void)initialize 
{ 
    // Copy methods from the default 
    copyMissingMethods([MyProtocol class], self); 
} 
@end 

Xcode le dará advertencias acerca de los métodos que faltan Foobar, pero puede ignorarlos

Esta técnica solo copia métodos, no ivars. Si los métodos están accediendo a miembros de datos que no existen, puede obtener errores extraños. Debe asegurarse de que los datos sean compatibles con el código. Es como si hicieras una reinterpretación_cast desde Foobar a MyProtocol.

Cuestiones relacionadas