iphone - 核心数据中的任意属性

标签 iphone core-data nsarray

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

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

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. 它可能会占用任意大量的内存,因此在处理大型数据集时会崩溃。

我愿意接受任何比剥离核心数据并直接使用 SQL 更容易的建议。非常感谢。

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

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

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 <NSSQLEntity Entry id=2> .

如文档中所述,只有一组固定的选择器可以使用 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

等等。

最佳答案

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

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

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

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

此外,我强烈建议研究两种方法 -valueForUndefineKey:-setValue: forUndefineKey: 作为更干净的解决方案问题。这将允许您编写如下代码:

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

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

更新

-valueForUndefinedKey: 设计仅用于代码中,不可用于访问存储。我对你的目标还是有点不清楚。

给定以下模型:

Entity <-->> Property

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

Key
Value

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

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

更新:重新考虑设计

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

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

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

关于iphone - 核心数据中的任意属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3561936/

相关文章:

ios - MagicalRecord 2.3.0/3.0 在对象中保存对象

iphone - 创建 NSObject 的副本

ios - 适用于 iOS 和 iPad 的 Storyboard

ios - 释放Core Foundation对象

iphone - iOS平移和缩放动画

ios - 不可见@interface声明选择器 'deleteAllObjects'

iphone - uitextfield隐藏键盘?

ios - 我如何在核心数据中保存图像然后检索它?使用 swift

iphone - NSMutablearray 将对象从索引移动到索引

ios - 对 JSON 日期进行排序