通过 plist 覆盖 NSUserDefaults 值

Posted

技术标签:

【中文标题】通过 plist 覆盖 NSUserDefaults 值【英文标题】:Overwrite NSUserDefaults values via plist 【发布时间】:2014-08-16 19:25:16 【问题描述】:

我创建了一个默认的首选项 plist 文件,当应用程序启动时,我注册了这些默认值。但我允许用户更改一个设置,该设置几乎更改了我注册的所有设置,除了更改每个单独的设置。基本上,我允许他们更改几乎所有其他存储设置的“主题”。当用户确实选择了一个主题时,而不是为每个设置调用setObject:forKey 并手动定义所选主题应该是什么,我想知道为每个主题创建另一个 plist 文件是否明智。我想我可以简单地覆盖键匹配的NSUserDefaults 中存储的值。这样做时,我还可以更轻松地检测应用程序的设置何时与任何主题的设置相同,或者他们是否自定义了主题。我会简单地检测存储在NSUserDefaults 中的值是否等于每个键的每个主题 plist 中存储的值,如果它们中的任何一个不同,我知道他们已经定制了它并且没有使用内置主题。如果我不使用 plist,我将不得不将每个存储的值与手动定义的值进行比较,因此在两个不同的位置定义该主题的默认值(当他们选择主题时我设置设置和我检查的位置查看当前设置是否与可用主题的设置相同)。

如果这是一个合适的实现,how does one overwrite existing values inNSUserDefaultsusing the values stored in a plist?如果没有,在这种情况下你会推荐什么?

【问题讨论】:

NSUserDefaults 不是数据库,也不是 DataModel 和持久性的替代品。 我知道。我将它用于可以在应用程序中更改的非常简单的设置(字体系列、颜色、粗细、大小等),不超过 12 个。碰巧一个“主题”设置会改变所有其他设置,看起来找到最好的方法。 我不知道我是否会为此使用plist。我将创建一个表示主题的数据模型,并让它为每个特定于主题的偏好返回特定值。然后有一个中心位置,您可以在其中查询指定值的当前主题,并将它们保存到NSUserDefaults。对于这个问题,多个plist 文件似乎是多余的。 【参考方案1】:

请看,设置是您的应用程序中的一个概念(存储和使用 UI 文本设置),最好在设计时考虑到从实际实现中抽象出来(保留在 .plist、数据库等中)。

所有这些单独的值都由一个概念组合在一起,因此可以由项目中的一个类来表示。这个类“总结”了与概念相关的实现细节。此逻辑与程序的其他部分隔离,当您需要更改它时,您的更改仅限于单个实体,因此每次更改都不太可能破坏您的其余代码。

这个类可以这样实现:

/////////////
// TextSettings.h

#import <Foundation/Foundation.h>

@interface TextSettings : NSObject <NSCoding>

@property (nonatomic, strong) UIColor *mainColor;
@property (nonatomic, copy) UIFont *font;

+ (instancetype)defaultTextSettings;

@end

/////////////
// TextSettings.m

@implementation TextSettings

+ (instancetype)defaultTextSettings

    TextSettings *textSettings = [[TextSettings alloc] init];
    textSettings.font = [UIFont systemFontOfSize:14.0f];
    textSettings.mainColor = [UIColor whiteColor];
    return textSettings;


#pragma mark - NSCoding

// Read more about NSCoding on: http://nshipster.com/nscoding/

- (id)initWithCoder:(NSCoder *)aDecoder

    self = [super init];
    if (self) 
        _font = [aDecoder decodeObjectForKey:@"_font"];
        _mainColor = [aDecoder decodeObjectForKey:@"_mainColor"];
    
    return self;


- (void)encodeWithCoder:(NSCoder *)aCoder

    [aCoder encodeObject:self.font forKey:@"_font"];
    [aCoder encodeObject:self.mainColor forKey:@"_mainColor"];


#pragma mark - Equality

- (BOOL)isEqual:(id)object

    if (object == nil) 
        return NO;
    

    if ([object isKindOfClass:[self class]] == NO) 
        return NO;
    

    TextSettings *otherTextSettings = object;

    return [self.font isEqual:otherTextSettings.font] && [self.mainColor isEqual:otherTextSettings.mainColor];


// You must override -hash if you override -isEqual
- (NSUInteger)hash

    return self.class.hash ^ self.font.hash ^ self.mainColor.hash;


@end

当你有这样一个对象时,你可以:

轻松测试它们是否相等 轻松归档和取消归档(序列化和反序列化)它们

平等测试

// TextSettings *myTextSettings
if ([myTextSettings isEqual:[TextSettings defaultTextSettings]]) 
    // User didn't change the textSettings...
 
else 
    // User changed the textSettings!

序列化/反序列化

// In a controller responsible for displaying something according to textSettings.
// textSettings (self.textSettings) is a @property in this controller.
- (void)saveCurrentTextSettings 

    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.textSettings];
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"currentTextSettings"];


- (void)loadTextSettings

    NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:@"currentTextSettings"];
    self.textSettings = [NSKeyedUnarchiver unarchiveObjectWithData:data];

差不多了。

当您想向 TextSettings 添加新字段时,您必须 (1) 将它们声明为 @properties,(2) 向 -isEqual:-hash 实现添加检查,以及 (3) 向-encodeWithCoder-initWithCoder

太多了!你可能会说,但我会说不——这并不是矫枉过正。绝对比在NSUserDefaults 中搜索和比较单个值更好。


UPD:

将其用作普通设置:

// Called when user chooses new value
- (void)userDidChooseFont:(UIFont *)font

    self.textSettings.font = font;
    [self saveTextSettings];


- (void)saveTextSettings

    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.textSettings];
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"textSettings"];

【讨论】:

那将是一个很好的解决方案。我想知道如果他们可能不使用任何主题,我将如何使用它。如果他们更改了一个选项,导致他们的设置未设置为任何主题的设置,那么将没有主题。因此,无论如何,我总是会在适当的地方获取单个值。 “主题”的想法只是一次更改多个选项的一站式商店,我从不在乎应用什么主题,除了在 UI 中指出。也许完全删除主题并仅支持更改个人设置会更好。 我还想提一下,它不是 UI 主题,而实际上只是用户可以应用覆盖图像的文本设置。有点像 snapchat 在从相机拍摄的图像上添加文本。 这个概念仍然是一样的——你创建一个数据模型来分组个人偏好。您的控制器或其他任何东西都可以设置其属性,然后将序列化写入某个持久存储。我已经更新了我的帖子。 至于默认值 - 更好,正如 Craig 所说,不要为此与 .plists 混淆。您可以在我的示例中定义像 +defaultTextSettings 这样的方法,它将创建一组默认的文本首选项。您可以在没有以前保存的设置时使用它。

以上是关于通过 plist 覆盖 NSUserDefaults 值的主要内容,如果未能解决你的问题,请参考以下文章

为啥不建议使用NSUserDefault存储大量数据

iOS 4.2 中的 NSUserDefault 问题

如何为 NSUserDefault Keys 设置初始值?

通过 plist 覆盖 NSUserDefaults 值

谈谈大家熟悉的NSUserDefault

通过 info.plist 中的 CFBundleDocumentTypes 覆盖 Mac 应用程序文件关联