核心数据中的任意属性

时间:2010-08-25 00:01:02

标签: iphone core-data nsarray

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

我目前的解决方案是与代表一对的另一个实体建立多对多的关系。在我的申请中,我有:

Entry <--->> ExtraAttribute

其中ExtraAttribute包含属性keyvalue,而key对于ExtraAttribute的条目是唯一的。

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

我需要按照该属性对具有给定ExtraAttribute的条目进行排序。使用SQL存储,Core Data本身显然不可能通过给定键的相关ExtraAttribute的值对条目进行排序。 (令人沮丧,因为这对其他商店来说是可能的,而在SQL本身也是如此。)

我能找到的唯一技术是自己对条目进行排序,然后将displayOrder属性写回商店,并按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. 可能会占用大量内存,因此会因大型数据集而崩溃。
  3. 我愿意接受任何比直接删除Core Data和直接使用SQL更容易的建议。非常感谢。

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

    以下是一个典型的数据集: n 条目对象,每个对象都有一个不同组的键/值对。在这里,我列出了每个条目下的键/值对:

    Entry 1:
        Foo => Hello world
        Bar => Lorem ipsum
    
    Entry 2:
        Bar => La dee da
        Baz => Goodbye cruel world
    

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

    使用-valueForUndefinedKey,SQLite存储无法按未知密钥排序:;尝试这样做会产生NSInvalidArgumentException,原因为keypath Foo not found in entity <NSSQLEntity Entry id=2>

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

    编辑2

    假设我的实体有三个实例E1,E2和E3,并且用户将自定义属性“Name”和“Year”附加到每个实例中。然后我们可能会:

    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
    

    等等。

1 个答案:

答案 0 :(得分:3)

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

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

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

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

此外,我高度建议您调查两种方法-valueForUndefinedKey:-setValue: forUndefinedKey:,以便更好地解决您的问题。这将允许您编写如下代码:

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

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

更新

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

鉴于以下模型:

Entity <-->> Property

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

Key
Value

在此处,您可以通过Entity访问-valueForUndefinedKey:上的任何媒体资源,因为在Entity下,Property会出去并获取该密钥的相关Entity。因此,您可以在Property上获得动态值。

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

更新:重新考虑设计

我提出的最后一个设计是错误的。在更深层次的反思中,它比我提出的更简单。您可以使用原始Property&lt; - &gt;&gt;完成目标。 -setValue: forKey:设计。但是,在-setValue: forKey:方法中还有一些工作要做。逻辑如下:

  1. Entity上名为-setValue: forKey:的外部代码。
  2. Property方法尝试检索Property
  3. 如果Property存在,则更新该值。
  4. 如果Property不存在,则会为每个Entity创建{{1}}并设置默认值(假设为空字符串)。
  5. 唯一的性能影响是引入新密钥时。除此之外,它应该没有任何性能惩罚。