【问题标题】:Setters, Getters and Exposing real core data types, how do I do that?Setter、Getter 和暴露真正的核心数据类型,我该怎么做?
【发布时间】:2014-09-11 07:11:27
【问题描述】:

我有一个核心数据实体,其结构如下:

number (integer 16),
reference (binary)
image (binary)

我已经为那个实体创建了一个类。

该类的标题具有实体属性的这些声明。

@property (nonatomic, retain) NSNumber * number;
@property (nonatomic, retain) NSData * reference;
@property (nonatomic, retain) NSData * image;

但其实这3个属性是

number = NSInteger
reference = NSArray
image = UIImage

因为我不能直接在核心数据上存储数组和图像,所以我必须将其转换为 NSData 来保存,但我不关心声明为 NSData 的属性,因为与 NSData 之间的转换是内部的实体,不应暴露给类外的代码。

我希望将这些标头属性声明为

@property (nonatomic, assign) NSInteger number;
@property (nonatomic, retain) NSArray * reference;
@property (nonatomic, retain) UIImage * image;

例如,当我将一个数组分配给reference 时,我想在内部将其转换为 NSData。

我知道我必须创建 setter 和 getter 才能做到这一点,但我的问题是实体已经在使用这些名称。显然,我可以将所有核心数据实体重命名为具有 xnumber、xreference、ximage 等前缀,因此我想公开的名称/类型与我想在内部隐藏的名称/类型之间不会发生冲突。

除此之外还有其他选择吗?我的恐惧以大量相似的参考文献结束。

【问题讨论】:

  • 您是否考虑过使用可转换类型 - NSValueTransformer ? ,就像他们在这里做的一样stackoverflow.com/questions/1598600/…
  • 我从未使用过它,但据我所知,如果我将这些值声明为可转换的,它们将像 id 一样暴露,我的问题仍然存在,对吧?
  • 从您的描述中很难判断这是否可行,但也许您的核心数据实体应该与单独的引用实体具有一对多关系,而不是尝试存储数组。此外,如果您对实体使用可转换类型,则可以将托管对象子类中的类型从 id 更改为您希望将其转换为的类型。如果对象符合 NSCoding,您甚至不需要自定义值转换器——系统足够聪明,知道必须将其转换为 NSData 对象。

标签: ios cocoa-touch cocoa core-data


【解决方案1】:

您在应用程序中所做的可能是使用核心数据最常见的失败。绝不应该鼓励您像以前那样直接使用核心数据类,始终使用子类化、类别或(最好的)使用包装器。

因为在大多数情况下,当您在模型中编辑实体时,您希望删除自动生成的文件并创建新文件,这些文件不应被修改。这是我不鼓励您使用任何快速修复(例如修改模型中的名称然后创建自定义设置器)的主要原因。

为什么我最推荐包装器是因为您可以使用它构建自己的界面。您可以根据需要在其上创建任意数量的方法、附件,可以使用数据保护,例如具有只读参数...因此,当您修改数据模型时,应用程序应该完全没有区别,当您可能会为一些优化或一些内部功能添加一些额外的表格,你不会有隐藏这些附件的问题。除此之外,拥有一个额外的层将使您可以非常轻松地创建一些缓存,轻松调试,因为您可以在或多或少的任何附件中放置断点或日志,您可以在内部维护多线程操作......

我现在可以理解,将您的代码迁移到其他系统可能需要一些时间,但这是应该考虑的事情。如果应用程序尚未完成,我建议您迁移它:如果您创建一个具有与应用程序中已使用的相同属性的包装器,则可以简单地更改它已经存在的类名使用,这应该不会花费太长时间。如果您选择继续按原样工作,您很可能会遇到一些更难的问题,如果没有其他问题,请记住这一点,当您开始一个新的应用程序时。

编辑:包装器说明和示例

包装器是指一个类实例,它包含另一个实例并围绕它构建一个接口。让我先给你看一个很好的例子:

界面:

@interface EntityWrapper : NSObject
@property NSInteger number;
@property UIImage *image;
+ (NSArray *)fetchAll;
+ (void)invalidateCache;
@end

实施:

@class EntityName;

static NSArray *__entityCache = nil;

@interface EntityWrapper() {
    EntityName *_boundEntity;
}
@end

@implementation EntityWrapper
- (instancetype)initWithEntity:(EntityName *)entity {
    if((self = [super init])) {
        _boundEntity = entity;
    }
    return self;
}
+ (NSArray *)fetchAll {
    if(__entityCache == nil) {
        NSMutableArray *toReturn = [[NSMutableArray alloc] init];

        NSArray *entityArray = nil; //fetch from data base
        for(EntityName *entity in entityArray)
            [toReturn addObject:[[EntityWrapper alloc] initWithEntity:entity]];

        __entityCache = [toReturn copy];
    }
    return __entityCache;
}
+ (void)invalidateCache {
    __entityCache = nil;
}

- (void)setNumber:(NSInteger)number {
    _boundEntity.number = @(number);
}
- (NSInteger)number {
    return [_boundEntity.number integerValue];
}
- (void)setImage:(UIImage *)image {
    _boundEntity.image = UIImagePNGRepresentation(image);
}
- (UIImage *)image {
    return [[UIImage alloc] initWithData:_boundEntity.image];
}
@end

正如您在此处看到的,我正在使用自定义 setter 和 getter 围绕实体构建一个界面。我什至创建了一个从数据库中获取所有对象的方法,下一步可能是使用一些谓词或者在这种情况下使用一些自定义选项来获取它们。我还添加了一个最简单的缓存,只是为了了解这个概念。

number 现在是NSInteger 而不是NSNumber,这可能非常方便,但要小心这样做,因为您可能需要知道number 是否为nil。对于这种情况,您还可以创建另一个属性,例如 BOOL numberIsSet

图片也差不多。您根本不需要转换器,只需要一个 getter 和一个 setter(这与转换器几乎相同,但这种方法更具动态性)。

因此,创建包装器可以为您提供终极力量。我们的想法是创建尽可能小且尽可能简单的界面。这意味着头文件中的方法与需要的方法一样多,其余部分是隐藏的。这些方法背后的逻辑可能非常复杂,但仍然可以维护,因为它是一个封闭系统(不依赖于实体,只依赖于实体),举个例子:

界面:

@property (readonly) NSDecimalNumber *heavyValue;

实施:

- (NSDecimalNumber *)heavyValue {
    NSDecimalNumber *valueA = _boundEntity.valueA;
    NSDecimalNumber *valueB = _boundEntity.valueB;
    NSDecimalNumber *valueC = _boundEntity.valueC;

    return [[valueA decimalNumberByAdding:valueB] decimalNumberByDividingBy:valueC];
}

现在这是一个相当标准的过程,并且效果很好,但是这种方法在 CPU 上可能非常繁重。如果此方法被多次调用,您可能会通过将结果存储到实体本身来进行优化。所以你要做的就是在模型heavyValue和代码中添加另一个值:

- (NSDecimalNumber *)heavyValue {
    NSDecimalNumber *toReturn = _boundEntity.heavyValue;
    if(toReturn == nil) {
        NSDecimalNumber *valueA = _boundEntity.valueA;
        NSDecimalNumber *valueB = _boundEntity.valueB;
        NSDecimalNumber *valueC = _boundEntity.valueC;

        toReturn = [[valueA decimalNumberByAdding:valueB] decimalNumberByDividingBy:valueC];
        _boundEntity.heavyValue = toReturn;
    }
    return toReturn;
}

- (void)setValueA:(NSDecimalNumber *)valueA {
    _boundEntity.valueA = valueA;
    _boundEntity.heavyValue = nil; //this invalidates the value
}

因此,在一个简单的 getter 背后,这是一个相当大的逻辑变化,但您的其余代码没有受到伤害,它仍然可以正常工作。

【讨论】:

  • 是的,我同意。你能举一个关于如何构建该包装器的代码示例吗?包装器是指 NSValueTransformer 吗?请显示一些代码。谢谢
  • 我编辑,添加了一个例子和解释。希望对你有帮助。
猜你喜欢
  • 1970-01-01
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 2010-11-08
  • 2017-01-28
  • 2016-06-17
  • 2012-08-30
相关资源
最近更新 更多