¿Alguna vez ha escrito adaptadores para Keychain o NSUserDefaults? Están completamente compuestos por los mismos setters y getters. Propongo escribir la lógica una vez, proporcionando el resto al tiempo de ejecución. Para la implementación, pido cat.

Hola Contigo otra vez vdugnist de FunCorp. Recientemente, al agregar un nuevo campo al adaptador de llavero, detecté un error al copiar el código de un método vecino.
Cómo se veía la implementación antes:
- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details }
El código simplemente se copió del método más cercano y las constantes cambiaron en él. Está claro que con este enfoque puede cometer un error fácilmente. Después de un poco de refactorización, resultó implementar toda la implementación de la lógica de clase en dos métodos:
- (Credentials *)credentials { return [self objectFromKeychainForKey:@"credentials"]; } - (void)setCredentials:(Credentials *)credentials { [self setObject:credentials toKeychainForKey:@"credentials"]; } - (NSDate *)firstLaunchDate { return [self objectFromKeychainForKey:@"firstLaunchDate"]; } - (void)setFirstLaunchDate:(NSDate *)firstLaunchDate { [self setObject:firstLaunchDate toKeychainForKey:@"firstLaunchDate"]; } - (void)setObject:(id)obj toKeychainForKey:(NSString *)key {
Ya mejor. Pero quedan dos problemas más:
- aún puede estar sellado en la constante de cadena que pasamos al método;
- toda la clase consistirá en métodos con la misma implementación, que solo difieren en el argumento del método llamado.
Y aquí viene ayuda para nosotros. En Objective-C, cuando se agrega @property a la interfaz de clase, el setter, getter y ivar se generan automáticamente. En la implementación del setter estándar, el valor se escribe en ivar, y para el getter, se lee desde ivar. Para que estos métodos no se generen, en la implementación de la clase debe escribir dinámico <nombre de campo>. Luego, al acceder al campo, obtendremos un selector no reconocido enviado a excepción de instancia.
Antes de enviar una excepción, la clase +(BOOL)resolveInstanceMethod:(SEL)sel
método +(BOOL)resolveInstanceMethod:(SEL)sel
en el caso de la propiedad de instancia o +(BOOL)resolveClassMethod:(SEL)sel
en el caso de la propiedad de clase.
En ellos, puede agregar la implementación del método mediante el selector usando class_addMethod
y devolver YES
si todo salió bien. Después de eso, se llamará a la implementación del método recién agregado para las llamadas actuales y posteriores.
Para agregar un método en tiempo de ejecución, necesitará un puntero a la clase a la que se agregará el método, un selector, un bloque de implementación y una firma de método. La documentación para el último argumento se puede encontrar aquí .
Inmediatamente decidí hacer una solución a mi problema en la biblioteca, por lo que tanto la propiedad de clase como la propiedad de instancia se procesaron en el ejemplo. El ejemplo utiliza funciones auxiliares, la implementación se puede encontrar aquí .
+ (BOOL)resolveClassMethod:(SEL)sel { return [self resolveMethodFor:object_getClass(self) selector:sel]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return [self resolveMethodFor:self selector:sel]; } + (BOOL)resolveMethodFor:(id)target selector:(SEL)sel { if (!sel_isGetterOrSetter(target, sel)) { return NO; } objc_property_t property = propertyForSelector(target, sel); if (sel_isSetter(target, sel)) { SEL getterSel = sel_getterFromSetter(sel); dvPropertySetterBlock setterBlock = [self setterBlockForTarget:target getterSelector:getterSel]; IMP blockImplementation = imp_implementationWithBlock(setterBlock); char *methodTypes = copySetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } else { dvPropertyGetterBlock getterBlock = [self getterBlockForTarget:target getterSelector:sel]; IMP blockImplementation = imp_implementationWithBlock(getterBlock); char *methodTypes = copyGetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } return YES; } + (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; }
En los herederos, es suficiente redefinir dos métodos (bloque getter y bloque setter), agregar @property a la interfaz y dinámico a la implementación. Aquí, por ejemplo, hay una implementación de un adaptador para NSUserDefaults:
+ (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^(id blockSelf, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:NSStringFromSelector(getterSelector)]; }; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^id(id blockSelf) { return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(getterSelector)]; }; }
La biblioteca en sí se puede encontrar en el github , y estoy listo para responder sus preguntas en los comentarios.