Same type methods? Stop writing

Have you ever written adapters for Keychain or NSUserDefaults? They consist entirely of the same type of setters and getters. I suggest writing the logic once, providing the rest to runtime. For the implementation of asking under the cat.


keyboard with copy and paste buttons


Hey. With you again vdugnist from FunCorp. Recently, when adding a new field to the Keychain adapter, I caught an error while copying code from a nearby method.


How did the implementation look like before:


- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details } 

The code was simply copied from the nearest method and the constants changed in it. It is clear that with this approach you can easily make a mistake. After a little refactoring, it turned out that the entire implementation of the class logic was put into two methods:


 - (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 } 

Already better. But there are still two problems:



And here we are come to the rescue rantaym. In Objective-C, when adding @property to the class interface, a setter, getter, and ivar are automatically generated. In the standard implementation of the setter, the value is written to ivar, and for the getter it is read from ivar. In order for these methods not to be generated, in the implementation of the class you need to write dynamic <field name>. Then, when we access the field, we get the exception unrecognized selector sent to instance.


Before sending an exception, the +(BOOL)resolveInstanceMethod:(SEL)sel in the instance property or +(BOOL)resolveClassMethod:(SEL)sel in the case of class property will be called on the class.
In them, you can add an implementation of the method on the selector using class_addMethod and return YES if everything went smoothly. After that, for the current and subsequent calls, the implementation of the newly added method will be called.


To add a method in runtime, you need a pointer to the class to which the method will be added, the selector, the block with the implementation, and the signature of the method. Documentation on the last argument can be read here .


I immediately decided to make a solution to my problem in the library, so in the example both the class property and instance property are processed. The example uses auxiliary functions, the implementation can be found here .


 + (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"; } 

In successors it is enough to override two methods (getter block and setter block), add @property to the interface and dynamic to the implementation. Here, for example, the implementation of the adapter to 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)]; }; } 

The library itself can be found on the githaba , and I am ready to answer your questions in the comments.

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


All Articles