O mesmo tipo de métodos? Pare de escrever

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.


teclado com botões de copiar e colar


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 { // implementation details } - (id)objectFromKeychainForKey:(NSString *)key { // implementation details } 

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.

Source: https://habr.com/ru/post/pt412549/


All Articles