Você já escreveu adaptadores para Keychain ou NSUserDefaults? Eles são completamente compostos pelos mesmos setters e getters. Proponho escrever a lógica uma vez, fornecendo o restante para o tempo de execução. Para implementação, peço gato.

Oi Com você novamente vdugnist da FunCorp. Recentemente, ao adicionar um novo campo ao adaptador de chaveiro, ocorreu um erro ao copiar o código de um método vizinho.
Como era a implementação antes:
- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details }
O código foi simplesmente copiado do método mais próximo e as constantes alteradas nele. É claro que, com essa abordagem, você pode facilmente cometer um erro. Depois de um pouco de refatoração, descobriu-se colocar toda a implementação da lógica de classe em dois 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 {
Já está melhor. Mas mais dois problemas permanecem:
- você ainda pode ser selado na constante de cadeia que passamos para o método;
- toda a classe consistirá em métodos com a mesma implementação, diferindo apenas no argumento do método chamado.
E aqui vem ajuda para nós. No Objective-C, quando @property é adicionado à interface da classe, o setter, getter e ivar são gerados automaticamente. Na implementação do setter padrão, o valor é escrito em ivar e, para o getter, é lido em ivar. Para que esses métodos não sejam gerados, na implementação da classe você precisa escrever <nome do campo> dinâmico . Em seguida, ao acessar o campo, um seletor não reconhecido será enviado para a exceção de instância.
Antes de enviar uma exceção, a classe +(BOOL)resolveInstanceMethod:(SEL)sel
método +(BOOL)resolveInstanceMethod:(SEL)sel
no caso da propriedade da instância ou +(BOOL)resolveClassMethod:(SEL)sel
no caso da propriedade da classe.
Neles, você pode adicionar a implementação do método pelo seletor usando class_addMethod
e retornar YES
se tudo correu bem. Depois disso, a implementação do método recém-adicionado será chamada para as chamadas atuais e subseqüentes.
Para adicionar um método em tempo de execução, você precisará de um ponteiro para a classe à qual o método será adicionado, um seletor, um bloco de implementação e uma assinatura de método. A documentação para o último argumento pode ser encontrada aqui .
Decidi imediatamente fazer uma solução para o meu problema na biblioteca, para que a propriedade da classe e a instância fossem processadas no exemplo. O exemplo usa funções auxiliares, a implementação pode ser encontrada aqui .
+ (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"; }
Nos herdeiros, basta redefinir dois métodos (bloco getter e bloco setter), adicionar @property à interface e dinâmico à implementação. Aqui, por exemplo, está uma implementação de um 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)]; }; }
A própria biblioteca pode ser encontrada no github , e estou pronto para responder às suas perguntas nos comentários.