相同类型的方法? 别写了

您是否编写过用于Keychain或NSUserDefaults的适配器? 它们完全由相同的setter和getter组成。 我建议只编写一次逻辑,其余部分提供给运行时。 为了实施,我要求猫。


带复制和粘贴按钮的键盘


你好 再次与您一起来自FunCorp的vdugnist。 最近,在向钥匙串适配器添加新字段时,从相邻方法复制代码时遇到错误。


实现之前的样子:


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

只需从最近的方法中复制代码,然后更改常量即可。 显然,使用这种方法可以轻松犯错。 稍作重构后,结果证明可以通过两种方法来实现类逻辑的整个实现:


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

已经更好了。 但是还有两个问题:


  • 您仍然可以将我们传递给该方法的字符串常量密封起来;
  • 整个类将由具有相同实现的方法组成,只是所调用方法的参数不同。

这是对我们的帮助。 在Objective-C中,将@property添加到类接口时,将自动生成setter,getter和ivar。 在标准的setter实现中,该值用ivar编写,而对于getter来说,它是从ivar读取的。 为了不生成这些方法,在类实现中,您需要编写动态的 <field name>。 然后,当访问该字段时,我们将获得一个无法识别的选择器,该选择器发送给实例异常。


在发送异常之前,对于实例属性,类将+(BOOL)resolveInstanceMethod:(SEL)sel方法;对于类属性,将+(BOOL)resolveInstanceMethod:(SEL)sel +(BOOL)resolveClassMethod:(SEL)sel
在它们中,您可以使用class_addMethod通过选择器添加方法实现,如果一切顺利,则返回YES 。 之后,将为当前调用和后续调用调用新添加的方法的实现。


要在运行时添加方法,您将需要一个指向要添加该方法的类的指针,一个选择器,一个实现块和一个方法签名。 最后一个参数的文档可以在这里找到。


我立即决定在库中解决我的问题,因此在示例中处理了class属性和instance属性。 该示例使用辅助功能,可以在此处找到实现。


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

在继承人中,足以重新定义两个方法(getter块和setter块),在接口中添加@property,并在实现中动态添加。 例如,这是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)]; }; } 

该库本身可以在github上找到,我准备在评论中回答您的问题。

Source: https://habr.com/ru/post/zh-CN412549/


All Articles