Avez-vous déjà écrit des adaptateurs pour Keychain ou NSUserDefaults? Ils sont entièrement composés des mêmes setters et getters. Je propose d'écrire la logique une fois, fournissant le reste à l'exécution. Pour la mise en œuvre, je demande cat.

Salut Avec vous à nouveau vdugnist de FunCorp. Récemment, lors de l'ajout d'un nouveau champ à l'adaptateur de trousseau, j'ai détecté une erreur lors de la copie du code d'une méthode voisine.
À quoi ressemblait l'implémentation auparavant:
- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details }
Le code a été simplement copié à partir de la méthode la plus proche et les constantes y ont été modifiées. Il est clair qu'avec cette approche, vous pouvez facilement faire une erreur. Après un peu de refactoring, il s'est avéré mettre en œuvre l'intégralité de l'implémentation de la logique de classe en deux méthodes:
- (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 {
Déjà mieux. Mais deux autres problèmes demeurent:
- vous pouvez toujours être scellé dans la constante de chaîne que nous passons à la méthode;
- toute la classe sera constituée de méthodes avec la même implémentation, ne différant que par l'argument de la méthode appelée.
Et voici venir nous aider. Dans Objective-C, lorsque @property est ajouté à l'interface de classe, le setter, le getter et l'ivar sont générés automatiquement. Dans l'implémentation de setter standard, la valeur est écrite dans ivar, et pour le getter, elle est lue dans ivar. Pour que ces méthodes ne soient pas générées, dans l'implémentation de classe, vous devez écrire un <nom de champ> dynamique . Ensuite, lors de l'accès au champ, nous obtiendrons un sélecteur non reconnu envoyé à l'exception d'instance.
Avant d'envoyer une exception, la classe +(BOOL)resolveInstanceMethod:(SEL)sel
méthode +(BOOL)resolveInstanceMethod:(SEL)sel
dans le cas de la propriété d'instance ou +(BOOL)resolveClassMethod:(SEL)sel
dans le cas de la propriété de classe.
Dans ceux-ci, vous pouvez ajouter l'implémentation de la méthode en sélectionnant en utilisant class_addMethod
et retourner YES
si tout s'est bien passé. Après cela, l'implémentation de la méthode nouvellement ajoutée sera appelée pour les appels actuels et suivants.
Pour ajouter une méthode lors de l'exécution, vous aurez besoin d'un pointeur sur la classe à laquelle la méthode sera ajoutée, d'un sélecteur, d'un bloc d'implémentation et d'une signature de méthode. La documentation du dernier argument peut être trouvée ici .
J'ai immédiatement décidé de résoudre mon problème dans la bibliothèque, de sorte que la propriété de classe et la propriété d'instance ont été traitées dans l'exemple. L'exemple utilise des fonctions auxiliaires, l'implémentation peut être trouvée ici .
+ (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"; }
Dans les héritiers, il suffit de redéfinir deux méthodes (bloc getter et bloc setter), ajouter @property à l'interface et dynamique à l'implémentation. Voici, par exemple, une implémentation d'un adaptateur pour 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 bibliothèque elle-même se trouve sur le github , et je suis prêt à répondre à vos questions dans les commentaires.