Objective-C 编写通用 getter 和 setter 方法

Objective-C Writing general getter and setter methods

在我的项目中,我有一个设置 class,其属性具有自定义 setter 访问 NSUserDefaults 的属性,使一切变得更简单。这个想法是 Settings class 有

@property NSString *name

具有从 NSUserDefaults 获取名称值的自定义 getter 和将新值保存在那里的 setter。通过这种方式,在整个项目中,我仅与设置 class 进行交互以管理用户定义的首选项。问题是写所有 getters 和 setters 似乎太重复了(我有大约 50 个属性),并且想创建一个 setter 和一个 getter 适用于所有变量。我唯一的问题是如何在 setter.

中获取变量的名称

那么最后一个问题是:是否有可能在 getter 或 setter 中找出哪个 属性 被调用的函数?

如果您有其他方法,我也会很感激,但考虑到我想将所有 NSUserDefaults 内容放在一个 class 中,我想不出一个替代方案包括写 50 getters 和 setters.

谢谢!

本例中的setter和getter很简单,可以这样操作:

- (void)setName:(NSString *)name {
   [[NSUserDefaults standardUserDefaults] setObject:name forKey:@"name"];
   [[NSUserDefaults standardUserDefaults] synchronize];
}

- (NSString *)name {
   return [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
}

如果您想对所有属性使用简单的方法:

- (id)objectForKey:(NSString *)key {
   return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
- (void)setObject:(id)object forKey:(NSString *)key {
   [[NSUserDefaults standardUserDefaults] setObject:object forKey:key];
   [[NSUserDefaults standardUserDefaults] synchronize];
}

与其创建许多属性,不如创建许多键,每个键都是您要保存或检索的内容。 键示例:

static NSString *const kName = @"name";
static NSString *const kLastName = @"lastName";

一种可能是使用 KVO 来检测您的属性何时更改。

例如:

@interface Settings : NSObject

@property NSString *one;
@property NSString *two;

@end

@implementation Settings

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self addObserver:self forKeyPath:@"one" options:NSKeyValueObservingOptionNew context:NULL];
        [self addObserver:self forKeyPath:@"two" options:NSKeyValueObservingOptionNew context:NULL];
    }
    return self;
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"one"];
    [self removeObserver:self forKeyPath:@"two"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"Key: %@, Change: %@", keyPath, change);
}

@end

在不同的 class 中,使用标准 属性 访问:

Settings *settings = [[Settings alloc] init];
settings.one = @"something for one";

设置对象日志:

Key: one, Change: { kind = 1; new = "something for one"; }

您可以尝试使用动态 Getter 和 Setter 声明 as noted in this answer.

首先创建您希望所有属性都使用的通用函数:

- (id)_getter_
{
    return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(_cmd)];
}

- (void)_setter_:(id)value 
{
    //This one's _cmd name has "set" in it and an upper case first character
    //This could take a little work to parse out the parameter name
    [[NSUserDefaults standardUserDefaults] setObject:object forKey:YourParsedOutKey];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

然后创建动态方法生成器:

+(void)synthesizeForwarder:(NSString*)getterName
{
    NSString*setterName=[NSString stringWithFormat:@"set%@%@:",
          [[getterName substringToIndex:1] uppercaseString],[getterName substringFromIndex:1]];
    Method getter=class_getInstanceMethod(self, @selector(_getter_));
    class_addMethod(self, NSSelectorFromString(getterName), 
                    method_getImplementation(getter), method_getTypeEncoding(getter));
    Method setter=class_getInstanceMethod(self, @selector(_setter_:));
    class_addMethod(self, NSSelectorFromString(setterName), 
                    method_getImplementation(setter), method_getTypeEncoding(setter));
}

然后设置要为其创建动态 getter 和 setter 的字符串:

+(void)load  
{
    for(NSString*selectorName in [NSArray arrayWithObjects:@"name", @"anything", @"else", @"you", @"want",nil]){
       [self synthesizeForwarder:selectorName];
    }
}

这将为您添加到数组中的任何变量名称创建 getter 和 setter。我不确定当其他 类 尝试调用这些方法时它的效果如何,编译器不会在编译时看到它们,因此当您尝试使用它们时可能会抛出错误。我刚刚合并了另外 2 个 Whosebug 针对您的情况提出了这个答案。

Dynamic Getters and Setters.

Get current Method name.

据我了解,您不希望此 class.

的用户调用 setObject:forKey:objectForKey: 方法的心理开销

这是绕过它的方法。我留下了很多空白让你填补。

  1. 在头文件中声明属性,方便调用者使用:

    @property NSString *something;
    @property NSString *somethingElse;
    
  2. 在 class 文件本身中,声明您正在定义属性,这样编译器就不会生气:

    @dynamic something,somethingElse;
    
  3. 在class文件中,实现methodSignatureForSelector函数,像这样:

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {if (SelectorIsGetter(aSelector))
       return [NSMethodSignature signatureWithObjCTypes:"@@:"];
     if (SelectorIsSetter(aSelector))
       return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
     return nil;
     }
    

这将告诉系统为这些选择器调用 forwardInvocation:,还将告诉它正在进行的调用的形式。

SelectorIsGetterSelectorIsSetter 的实施由您决定。您可能会使用 NSStringFromSelector(aSelector) 来获取选择器的名称,然后在 table 名称中查找它是否与您正在实现的选择器的任何名称相匹配:在这种情况下 somethingsomethingElse 用于 getter,setSomething:setSomethingElse: 用于 setter。

  1. 在class文件中,实现forwardInvocation:函数,像这样:

    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {if (SelectorIsGetter(anInvocation.selector))
       {NSString *s=[self objectForKey:NSStringFromSelector(anInvocation.selector)];
        [anInvocation setReturnValue:&s];
        return;
        };
     if (SelectorIsSetter(anInvocation.selector))
        {NSString *s;
         [anInvocation getArgument:&s atIndex:2];
         [self setObjectForKey:UnmangleName(NSStringFromSelector(anInvocation.selector))];
         return;
         };
     [super forwardInvocation:anInvocation];
     }
    

…其中 UnmangleName 是一个非常乏味的函数,它将像 "setSomething:" 这样的字符串转换成像 "something".

这样的字符串

如果你想做的不仅仅是 NSStrings,扩展相当简单。

我发现你的问题很有趣,我对自己说 "Challenge accepted!"。

我在 Github 上创建了 this 项目。

基本上,您所要做的就是子class VBSettings class,然后声明属性,如下所示:

@interface MySettings : VBSettings

@property (strong, nonatomic) NSString *hello;

@end

"hello" 的值将使用键 "hello" 保存到 NSUserDefaults。使用示例:

MySettings settings = [[MySettings alloc] init];
settings.hello = "World!"; //The value is saved in NSUserDefaults
NSLog(@"%@", settings.hello); //The value is restored from NSUserDefaults.

另一种方法可能是这样。 没有属性,只有键值下标。

@interface DBObject : NSObject<NSCoding>
+ (instancetype)sharedObject;
@end

@interface NSObject(SubScription) 
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
@end

关于实现文件:

+ (instancetype)sharedObject {
   static DBObject *sharedObject = nil;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
     sharedObject = [[DBObject alloc] init];
   });
   return sharedObject;
}

- (id)objectForKeyedSubscript:(id)key {
   return [[NSUserDefaults standardUserDefaults] objectForKey:key];
 }

- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key {
   [[NSUserDefaults standardUserDefaults] setObject:obj forKeyedSubscript:key];
   [[NSUserDefaults standardUserDefaults] synchronize];
 }

现在,您可以像这样使用它:

 // You saved it in NSUserDefaults
[DBObject sharedObject][@"name"] = @"John"; 

// You retrieve it from NSUserDefaults
NSLog(@"Name is: %@", [DBObject sharedObject][@"name"]); 

我认为这是最好的方法,也是我将来会使用的方法。