【问题标题】:Arbitrary Attributes in Core Data核心数据中的任意属性
【发布时间】:2011-04-03 11:31:21
【问题描述】:

简而言之,我想在 iPad 应用上将任意键/值对与 Core Data 实体的对象相关联。

我当前的解决方案是与另一个代表一对的实体建立一对多关系。在我的应用程序中,我有:

Entry <--->> ExtraAttribute

其中ExtraAttribute 具有属性keyvalue,而key 对ExtraAttribute 的条目是唯一的。

虽然处理这个的代码有点复杂,但还是可以接受的。真正的问题在于排序

我需要按该属性对具有给定 ExtraAttribute 的那些条目进行排序。使用 SQL 存储,Core Data 本身显然不可能通过与给定键关联的 ExtraAttribute 的值对条目进行排序。 (令人沮丧,因为这在其他存储中是可能的,并且在 SQL 本身中微不足道。)

我能找到的唯一技术是自己对条目进行排序,然后将displayOrder 属性写回存储区,并让Core Data 按displayOrder 排序。我在Entry 上使用以下类方法来做到这一点。 (这使用了一些未显示的方法和全局函数,但希望您能了解要点。如果没有,请询​​问,我会澄清。)

NSInteger entryComparator(id entry1, id entry2, void *key) {
    NSString *v1 = [[entry1 valueForPropertyName:key] description];
    NSString *v2 = [[entry2 valueForPropertyName:key] description];
    return [v1 localizedCompare:v2];
}

@implementation Entry

...

// Unified builtin property and extraAttribute accessor;
// expects human-readable name (since that's all ExtraAttributes have).
- (id)valueForPropertyName:(NSString *)name {
    if([[Entry humanReadablePropertyNames] containsObject:name])  {
        return [self valueForKey:
            [Entry propertyKeyForHumanReadableName:name]];
    } else {
        NSPredicate *p = [NSPredicate predicateWithFormat:
            @"key = %@", name];
        return [[[self.extraAttributes filteredSetUsingPredicate:p]
                                            anyObject] value];
    }
}

+ (void)sortByPropertyName:(NSString *)name
        inManagedObjectContext:(NSManagedObjectContext *)moc {
    BOOL ascending = [Entry propertyIsNaturallyAscending:name];
    [Entry sortWithFunction:entryComparator
        context:name ascending:ascending moc:moc];
    [[NSUserDefaults standardUserDefaults]
        setObject:name
        forKey:@"entrySortPropertyName"];
}

// Private method.
+ (void)sortWithFunction:(NSInteger (*)(id, id, void *))sortFunction
        context:(void *)context
        ascending:(BOOL)ascending
        moc:(NSManagedObjectContext *)moc {

    NSEntityDescription *entityDescription = [NSEntityDescription
        entityForName:@"Entry" inManagedObjectContext:moc];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];
    NSError *error;
    NSArray *allEntries = [moc executeFetchRequest:request error:&error];
    [request release];
    if (allEntries == nil) {
        showFatalErrorAlert(error);
    }

    NSArray *sortedEntries = [allEntries
        sortedArrayUsingFunction:sortFunction context:context];

    int i, di;
    if(ascending) {
        i = 0; di = 1;
    } else {
        i = [sortedEntries count]; di = -1;
    }
    for(Entry *e in sortedEntries) {
        e.displayOrder = [NSNumber numberWithInteger:i];
        i += di;
    }
    saveMOC(moc);
}

@end

这有两个主要问题:

  1. 速度很慢,即使是小型数据集。
  2. 它可能会占用任意大量内存,因此在处理大型数据集时会崩溃。

我愿意接受任何比删除 Core Data 和直接使用 SQL 更容易的建议。非常感谢。

编辑感谢您的回答。希望这能澄清问题。

这是一个典型的数据集:有 n 个 Entry 对象,每个对象都有一个 distinct 组与之关联的键/值对。这里我列出了每个条目下的键/值对:

Entry 1:
    Foo => Hello world
    Bar => Lorem ipsum

Entry 2:
    Bar => La dee da
    Baz => Goodbye cruel world

在这里,我想按“Foo”、“Bar”或“Baz”中的任何一个键对条目进行排序。如果给定的条目没有键的值,它应该像一个空字符串一样排序。

SQLite 存储无法使用 -valueForUndefinedKey: 来按未知键排序;尝试这样做会导致NSInvalidArgumentException,原因keypath Foo not found in entity &lt;NSSQLEntity Entry id=2&gt;

如文档中所述,只有一组固定的选择器可用于使用 SQL 存储的排序描述符。

编辑 2

假设我的实体有三个实例 E1、E2 和 E3,并且用户将自定义属性“名称”和“年份”附加到每个实例。那么我们可能有:

E1    Bob      2010
E2    Alice    2009
E3    Charles  2007

但我们希望将这些实例呈现给用户,并按这些自定义属性中的任何一个进行排序。例如,用户可能按名称排序:

E2    Alice    2009
E1    Bob      2010
E3    Charles  2007

或按日期:

E3    Charles  2007
E2    Alice    2009
E1    Bob      2010

等等。

【问题讨论】:

  • 您是否为displayOrder 属性编制了索引?这可以显着提高性能。
  • 您尝试过使用 NSSortDescriptor 吗? (developer.apple.com/mac/library/documentation/Cocoa/Reference/…)
  • 我已将displayOrder 编入索引。需要明确的是,缓慢的、消耗记忆的部分正在诉诸一个新的属性。 Core Data 通过 displayOrder 很好地检索预先排序的条目。我努力使用NSSortDescriptor 和Core Data 的内置排序功能,但无法正常工作。如果需要,我可以解释我尝试过的一切;但是,我已经有一段时间没有解决这个问题了,我不得不重新创建大部分试验。
  • 看看这个问题:stackoverflow.com/questions/2013158/… 这是如何使用自定义方法进行排序。看看这个 sn-ps,他们可能会解决你的问题:developer.apple.com/iphone/library/documentation/DataManagement/…

标签: iphone core-data nsarray


【解决方案1】:

第一个问题是,为什么需要将排序存储在数据库中?如果您总是在 key 属性中进行排序,那么只要您需要按排序顺序访问它们,只需使用排序描述符即可。

第二个问题,你为什么要编写自己的排序例程?

这个设计看起来相当复杂。我了解任意存储键值对的需求,我在书中设计了一个类似的系统。但是,我不清楚是否需要对这些值进行排序,也不清楚是否需要像这样的自定义排序例程。

如果您能解释排序背后的需求,我可能会建议一个更好的策略。

另外,我强烈建议研究-valueForUndefinedKey:-setValue: forUndefinedKey: 这两种方法,作为解决您问题的更清洁的方法。这将允许您编写如下代码:

[myObject valueForKey:@"anythingInTheWorld"];
[myObject setValue:someValue forKey:@"anythingInTheWorld"];

并遵循正确的键值编码规则。

更新

-valueForUndefinedKey: 设计仅用于代码,不用于访问商店。我还是有点不清楚你的目标。

给定以下模型:

Entity <-->> Property

在这个设计中,Property 有两个属性:

Key
Value

从这里,您可以通过-valueForUndefinedKey: 访问Entity 上的任何属性,因为在幕后,Entity 将出去并为该密钥获取关联的Property。因此,您可以在 Entity 上获得动态值。

现在是排序问题。通过这种设计,您可以直接在 SQLite 上进行排序,因为您实际上是在 Property 实体上进行排序。虽然我仍然不清楚排序的最终目标。它有什么价值?将如何使用?

更新:重新考虑设计

我提出的最后一个设计是错误的。经过更深入的思考,它比我提出的要简单。您的目标可以通过最初的Entity > Property 设计来实现。然而,-setValue: forKey: 方法还有一些工作要做。逻辑如下:

  1. Entity 上称为 -setValue: forKey: 的外部代码。
  2. -setValue: forKey: 方法尝试检索 Property
  3. 如果Property 存在,则更新该值。
  4. 如果 Property 不存在,则为每个 Entity 创建一个 Property,并设置默认值(假设为空字符串)。

唯一的性能损失是引入新密钥时。除此之外,它应该可以在没有任何性能损失的情况下工作。

【讨论】:

  • 我希望你能出现。我尝试了 -valueForUndefinedKey: 方法。但是,SQLite 存储不能按数据模型中没有以常规方式给出的键进行排序。我将更新问题,详细说明我的意图和我尝试过的方法。
  • 感谢您再次回复。它是基本的应用程序功能。我正在编写一个应用程序,用户可以在其中将任意属性附加到对象。然后,用户必须能够看到按给定属性值排序的对象列表。例如,用户可能会将属性“名称”附加到对象的子集;然后,他们希望看到按名称按字母顺序排列的对象。 (实际的属性名称是艺术术语,我无法预测。)我不知道如何通过对属性对象本身进行排序来实现这一点。
  • 感谢您抽出这么多时间来回答这个问题。不幸的是,如果没有一些评论,我不会理解你的图表。至于您的问题,我需要对任何用户定义属性的值以及我的实体的一些内置属性进行排序(但第二部分很容易,可以单独实现)。我在原始问题中附加了一个示例。
  • 我现在正在研究其他一些代码,但我会试试这个并在下周回复您。再次感谢你的帮助。我会尝试你的建议,但我希望从 SQL 存储中得到一个异常,说它不能对不“真正存在”的键进行排序。 (我忘记了确切的措辞。)Ciao 现在。
  • SO 会在您回复时通知我,如果您关注我的最新更新,它将起作用,因为密钥将存在。您一次为所有实体创建它们。
猜你喜欢
  • 2012-08-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-30
  • 2012-03-02
  • 2015-04-24
  • 1970-01-01
  • 2016-05-07
  • 2012-10-12
相关资源
最近更新 更多