2010-08-24 9 views
16

Supongamos (por razones de argumento) que tengo una clase de vista que contiene un NSDictionary. Quiero un montón de propiedades, todas las cuales acceden a los miembros de ese diccionario.Escribo mis propias propiedades @dynamic en Cocoa

Por ejemplo, quiero @property NSString* title y @property NSString* author.

Para cada una de estas propiedades, la implementación es la misma: para el getter, llame al [dictionary objectForKey:propertyName];, y para el setter haga lo mismo con setObject: forKey :.

Tomaría un montón de tiempo y usaría un montón de código de copiar y pegar para escribir todos esos métodos. ¿Hay alguna manera de generarlos automáticamente, como Core Data con las propiedades @dinámicas de las subclases NSManagedObject? Para ser claro, solo quiero este medio de acceso para las propiedades que defino en el encabezado, no solo cualquier clave arbitraria.

Me he encontrado con valueForUndefinedKey: como parte de la codificación de valores clave, que podría manejar los captadores, pero no estoy del todo seguro de si este es el mejor camino a seguir.

Necesito que estas sean propiedades explícitas, así que puedo unirlas en Interface Builder: eventualmente planeo escribir una paleta IB para esta vista.

(Por cierto, sé que mi ejemplo de utilizar un NSDictionary para almacenar estos es un poco artificial. De hecho, estoy escribiendo una subclase de WebView y las propiedades se referirán a los ID de elementos HTML, pero eso no es importante para el ¡lógica de mi pregunta!)

Respuesta

17

me las arreglé para resolver esto por mí mismo después de verter sobre la documentación Objective-C en tiempo de ejecución.

he implementado este método de clase:

+ (BOOL) resolveInstanceMethod:(SEL)aSEL 
{ 
    NSString *method = NSStringFromSelector(aSEL); 

    if ([method hasPrefix:@"set"]) 
    { 
     class_addMethod([self class], aSEL, (IMP) accessorSetter, "[email protected]:@"); 
     return YES; 
    } 
    else 
    { 
     class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:"); 
     return YES; 
    } 
    return [super resolveInstanceMethod:aSEL]; 
} 

Seguido por un par de funciones C:

NSString* accessorGetter(id self, SEL _cmd) 
{ 
    NSString *method = NSStringFromSelector(_cmd); 
    // Return the value of whatever key based on the method name 
} 

void accessorSetter(id self, SEL _cmd, NSString* newValue) 
{ 
    NSString *method = NSStringFromSelector(_cmd); 

    // remove set 
    NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""]; 

    // Set value of the key anID to newValue 
} 

Dado que este código intenta aplicar cualquier método que se llama en la clase y no se ha implementado , causará problemas si alguien intenta llamar algo que está esperando. Planeo agregar algunas verificaciones de cordura, para asegurarme de que los nombres coincidan con lo que estoy esperando.

+2

El único cambio que haré es minúsculas en la primera letra (y asegúrese de que la cadena _cmd tenga al menos 5 caracteres): NSString * property = NSStringFromSelector (_cmd); NSString * firstLetter = [[propiedad substringWithRange: NSMakeRange (3, 1)] lowercaseString]; property = [firstLetter stringByAppendingString: [property substringWithRange: NSMakeRange (4, [property length] - 5)]]; – charles

+0

Gran ejemplo. Había llegado a un tipo de IMP congelado (es decir, para 'setName:' ​​mi IMP era 'setValueForName') pero su uso de' NSStringFromSelector (_cmd) 'me dio exactamente la patada en los pantalones mentales que estaba buscando. generalice esto a 'setWhatever:'. Gracias por escribirlo. – matt

0

suena como deberías estar usando una clase en lugar de un diccionario. te estás acercando a la implementación manual de lo que el lenguaje intenta darte.

+1

Sí, la cosa del diccionario fue un ejemplo artificial. De hecho, estoy usando esto para hacer una vista de un documento HTML al que puedes conectarte con enlaces. Tienes razón en que si eso es todo lo que estaba haciendo, sería un mal plan :) –

2

Se puede utilizar una combinación de las opciones sugeridas:

  • utilizar la palabra clave @dynamic
  • sobreescritura valueForKey: y setValue: forKey: para acceder al diccionario
  • utilizan los Objective-C reflexión de la API método class_getProperty y verifíquelo nulo. Si no es nada, tu clase tiene esa propiedad. No lo es si lo es.
  • luego llame al método súper en los casos donde no exista tal propiedad.

Espero que esto ayude. Puede parecer un poco hacky (usando el reflejo) pero en realidad esta es una solución muy flexible y absolutamente "legal" al problema ...

PD: la forma de coredata es posible pero sería una exageración total en su caso ...

+0

Agregaré el bit class_getProperty a mi solución, creo. Gracias. –

2

Befriend a Macro? Esto puede no ser 100% correcto.

#define propertyForKey(key, type) \ 
    - (void) set##key: (type) key; \ 
    - (type) key; 

#define synthesizeForKey(key, type) \ 
    - (void) set##key: (type) key \ 
    { \ 
     [dictionary setObject];// or whatever \ 
    } \ 
    - (type) key { return [dictionary objectForKey: key]; } 
Cuestiones relacionadas