序列化包含对核心数据中 NSManagedObjects 的引用的多维数组

Posted

技术标签:

【中文标题】序列化包含对核心数据中 NSManagedObjects 的引用的多维数组【英文标题】:Serializing a multidimensional array containing references to NSManagedObjects in Core Data 【发布时间】:2014-03-09 15:05:20 【问题描述】:

我有两个实体,ChainStepChain有一个属性steps,可以是Step实体的多维数组,例如:

[
  step,
  step,
  step,
  [
    step,
    step
  ],
  step
]

每个Step 对象都有一个content 属性,它是一个字符串。

如果我使用的是关系数据库,我只需将该数组存储为 JSON,每个 step 都是特定步骤的 step_id

如何在 Core Data 中实现类似的功能?我认为我需要使 Step 类符合 NSCoding 协议,但那会是什么样子呢?我如何让它只在 Chain.steps 的最终值中存储其 id 的等价物,即对自身的引用?

编辑

下面的评论表明我在Step 和其他Steps 之间包含一对多关系(我们称之为nextStep)。然后我将使用这种关系在Chain 中从一个步骤转到下一个步骤,因此Chain 实体只需要包含序列中的第一个Step。这样做的问题是Step 可能属于多个Chain,因此它可能并不总是具有相同的nextStep 值。

【问题讨论】:

如果这个问题“过于宽泛”,正如投票结果所暗示的那样,请告诉我我能做些什么来纠正这个问题。 我不是反对者,我认为这是一个有趣的问题。但是,如果您添加有关您迄今为止尝试过的内容的详细信息(和图片)(您必须根据您的 cmets 完成),这将有所帮助。这也将有助于减少人们对潜在解决方案的关注。说了这么多,我怀疑您将需要一位核心数据专家来回答这个问题(可能是 Marcus S. Zarra,他在核心数据上“写了这本书”) 您能否将您的实体关系视为Step of one or more Steps?在这种情况下,您将拥有一个实体Step,其属性为content,并且与自身的关系为one-to-many,称为nextStep(或其他东西)。然后您需要遍历关系(检查contentnextStep)以发现您是否是分支的底部。这只是我基于上述信息的想法(作为核心数据业余爱好者)。 @RoboticCat - 这是一个有趣的建议,但不适用于此原因,因为单个 Step 可能属于多个 Chains,因此 nextStep 值可能并不总是相同.关于添加有关我尝试过的更多详细信息,好吧,我还没有真正尝试过任何事情,因为我不知道如何序列化对象引用的多维数组 我可以给你一个使用归档和NSValueTransformer 的解决方案,但由于数组包含核心数据对象,我会敦促你重新考虑一种不同的方法。您有兴趣考虑不同的解决方案吗? 【参考方案1】:

我不鼓励您对包含 Core Data 对象的数组进行序列化和反序列化。有可能,您可以转换数组以保存步骤的对象 ID 的 URIRepresentation,但是如果您从数据库中删除一个步骤会发生什么情况?您的序列化数组仍然被旧对象污染。


这是我的建议。

https://github.com/LeoNatan/ChainStepsExample

我的模型是这样设置的:

Link 是一个抽象实体。

为了方便使用,我定义了如下协议:

@protocol Link <NSObject>

//This is to make chain traversal easy - all links are guaranteed to have "steps".
- (NSOrderedSet*)steps;

@end

为了创建,我添加了以下便利工厂方法:

@interface Link : NSManagedObject <Link>

+ (id<Link>)linkWithStepsArray:(NSArray*)stepsArray inContext:(NSManagedObjectContext*)context;
+ (id<Link>)linkWithStepsOrderedSet:(NSOrderedSet*)stepsOrderedSet inContext:(NSManagedObjectContext*)context;
+ (id<Link>)linkWithStep:(Step*)step inContext:(NSManagedObjectContext*)context;

@end

现在,链的创建和遍历非常容易。

Step* step1 = [Step newObjectInContext:context];
step1.content = @"Wake up";

Step* step2_1 = [Step newObjectInContext:context];
step2_1.content = @"Go to school";

Step* step2_2 = [Step newObjectInContext:context];
step2_2.content = @"Learn new things";

Step* step3 = [Step newObjectInContext:context];
step3.content = @"Go to sleep";

NSOrderedSet* links = [NSOrderedSet orderedSetWithObjects:[Link linkWithStep:step1 inContext:context], [Link linkWithStepsArray:@[step2_1, step2_2] inContext:context], [Link linkWithStep:step3 inContext:context], nil];
[chain setLinks:links];

最后遍历:

for(Chain* chain in chains)

    NSLog(@"<chain %@>", chain.name);

    for(id<Link> link in chain.links)
    
        NSLog(@"\t<step>");
        for(Step* step in link.steps)
        
            NSLog(@"\t\t%@", step.content);
        
        NSLog(@"\t</step>");
    

    NSLog(@"</chain>\n\n");

这为您提供了一个可管理的对象图,当一个任务被删除时,它会自动重建。

这是一个简单的例子。它只解决了一个层次。您可以在SingleStepLinkMultiStepLinkLink(而不是Step)之间建立一个无限深的关系,叶子最终与Step 建立关系。

【讨论】:

【参考方案2】:

简短的回答: 由@Leo Natan 给出:您可以为链接到Chain 的每个Steps 构建一个URIRepresentation 的多维数组。 这个解决方案很难维护(没有自动级联等)。

问题: 使用 CoreData 框架表示多维数组 ... 没有简单的实现,因为 CoreData 是一个对象图持久层,而您需要一个具有严格顺序并且可能包含节点重复的数据结构。 此外,您必须定义您可能希望实现和优化表示以支持特定功能的这种数据结构的用途。

我的建议: 由于您需要“数组”和“对象复制”,因此您应该部分放弃直接关系建模并引入中间实体(我们称之为StepPlaceholder)。 如果需要,此实体将处理订单和复制。 由于您尚未指定对这种结构的用途,因此我考虑优化读取操作和增量增长。 因此,我们将使用NSIndexPath 的形式将您的Steps 存储在一个稀疏的N 维矩阵中

我的模特:

binaryIndexPath 属性是一个序列化的NSUInteger C 样式数组,其中数组中的每个索引都是数组矩阵的维度索引(StepChain 中的最终位置)。

indexPath 属性是暂时的,仅用作将索引路径转换为 ​​NSData 的实用程序(不是必需的,只是一个示例)。

编辑: 显然,我的实现中存在字节顺序问题。因此,为了解决它,我们必须在将每个索引保存到存储之前翻转每个索引中的字节顺序。 (如果我们使用内存中排序,这可以完全避免,如果有的话,性能不会下降太多)。

setter的实现是:

//byte order correction
NSUInteger flipNSUInteger(NSUInteger input) 
    uint8_t* buff = (uint8_t*)(&input);
    uint8_t tmp;
    size_t size = sizeof(input);
    for (NSUInteger i = 0; i < size/2; ++i) 
        tmp = buff[i];
        buff[i] = buff[size-i-1];
        buff[size-i-1] = tmp;
    
    return input;


- (void) setIndexPath:(NSIndexPath*)indexPath

    NSIndexPath* currentIndexPath = [self indexPath];
    if ( currentIndexPath==nil ||
        [currentIndexPath compare:indexPath] != NSOrderedSame)
    
        [self willChangeValueForKey:@"indexPath"];
        if (indexPath) 
            NSUInteger* bytes = (NSUInteger*)malloc([indexPath length]*sizeof(NSUInteger));
            [indexPath getIndexes:bytes];
            for (NSUInteger i = 0; i < indexPath.length; ++i) 
                bytes[i] = flipNSUInteger(bytes[i]);
            
            self.binaryIndexPath = [NSData dataWithBytesNoCopy:bytes
                                                        length:[indexPath length]*sizeof(NSUInteger)];
         else 
            self.binaryIndexPath = nil;
        
        [self setPrimitiveValue:indexPath forKey:@"indexPath"];
        [self didChangeValueForKey:@"indexPath"];
    

现在,为什么我们需要所有这些?因为我们喜欢在 binaryIndexPath blob 属性上对 StepPlaceholders 进行排序。

例如,在您的上述用例中(假设只有一个名为 S 的 Step 正在填充矩阵)您的索引路径将是:

[S : (0)], [S : (1)], [S - (2)], [S - (3,0)], [S - (3,1)], [S - (4)]

排序顺序类似于字符串排序(首先比较 MSB)。

现在剩下要做的就是获取所有附加到给定链的StepPlaceholders,这些链按binaryIndexPath 属性排序,如下所示:

NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:@"StepPlaceholder"];
r.predicate = [NSPredicate predicateWithFormat:@"chain = %@",self.objectID];
r.relationshipKeyPathsForPrefetching = @[@"step"];
r.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"binaryIndexPath"
                                                    ascending:YES]];

该请求经过优化以获取相关的 Steps 以及占位符。 我将它添加为Chain 实体上的一种方法,因此是self.objectID。 如果您对二进制属性进行排序似乎不稳定,您还可以使用 indexPath 瞬态属性获取占位符并在内存中对它们进行排序(不难实现)。

现在我们将使用Chain上的类方法按需生成多维数组: (以及对NSArray 的一些补充)

static char const* const virtual_last_index_key = "virtual_last_index";
@interface NSArray (lastvirtualindex)
@property (nonatomic,assign) NSUInteger virtualLastIndex;
@end

@implementation NSArray (lastvirtualindex)
- (NSUInteger) virtualLastIndex

    return [objc_getAssociatedObject(self, virtual_last_index_key) unsignedIntegerValue];


- (void) setVirtualLastIndex:(NSUInteger)virtualLastIndex

    objc_setAssociatedObject(self, virtual_last_index_key, @(virtualLastIndex),OBJC_ASSOCIATION_RETAIN_NONATOMIC);

@end


+ (NSArray*) buildMultiDimArrayWithSortedPlaceholders:(NSArray*)placeholders

    NSMutableArray* map = [NSMutableArray new];
    for (StepPlaceholder* placeholder in placeholders) 
        NSMutableArray* currentMap = map;
        NSUInteger i = 0;
        NSUInteger length = [placeholder.binaryIndexPath length]/(sizeof(NSUInteger));
        const NSUInteger* indexes = [placeholder.binaryIndexPath bytes];
        for (;i < length-1; ++i) 
            if ([currentMap count] &&
                currentMap.virtualLastIndex == indexes[i] &&
                [[currentMap lastObject] isKindOfClass:[Step class]])
            
                return nil;
             else if ([currentMap count] == 0 || currentMap.virtualLastIndex != indexes[i]) 
                [currentMap addObject:[NSMutableArray new]];
            
            currentMap.virtualLastIndex = indexes[i];
            currentMap = currentMap.lastObject;
        
        [currentMap addObject:[placeholder step]];
        currentMap.virtualLastIndex = indexes[i];
    
    return map;

您现在可以组合一个多维数组的瞬态属性。

该算法经过构建,因此您不必为对象创建连续索引,只需单调递增的索引路径表示即可。

缺点: 由于数组数据结构,向现有映射插入步骤并不容易(您需要更新同一数组中所有元素的占位符)。

很难确保 Step 在此表示中的唯一性(但由于此模型必须允许 Steps 的重复,这应该不是什么大问题)。

您必须保存索引。在没有适当调整索引的情况下删除StepStepPlaceholder 将在索引连续性中创建“漏洞”(1,2,7 而不是1,2,3)。算法和表示不会受到漏洞的影响,但它并不漂亮:)

优点:

数据的真实多维表示。

比关系遍历更容易构造。

效率更高(性能方面)然后逐个对象(或逐个维度)遍历集合对象,因为其他实现在Step 和它所属的Chain 之间没有直接链接,并且会发生更多错误经常。

允许在同一维度/数组中重复一个步骤。

可以找到示例项目HERE

【讨论】:

【参考方案3】:

请考虑使用CoreData 关系,因此Chain 将有序关系到Step 实体,这将具有类型到一的反向关系到Chain 实体。

【讨论】:

只有在 steps 属性是一维数组时才有效。 您可以将有序对多关系添加到指向 Step 实体的 Step 实体,这将表示父步骤中的步骤链。 我认为这与@RoboticCat 在我帖子下的 cmets 中提出的建议相同。这是一个好主意,但请参阅我对他的回复,了解为什么它不起作用。不过谢谢! 哦,我明白了。很抱歉没有专心。这是我在你的情况下要做的:我会根据 ChainID 或 parentStep ID 构建 StepID,因此所有步骤都是唯一的,并且不同的链不会有相同的步骤,因为对我来说这是不对。如果我不清楚,请告诉我 谢谢,但我需要允许一个步骤在多个链中。【参考方案4】:

这个怎么样? Chain 与 step 实体是一对多的关系(例如 stepsOfChain > chainOfStep),而 step 与自身也有一对多的关系(例如 stepsOfStep > stepOfStep)。后一种关系开启了在 stepsOfChain 字段的一个条目中存储多个步骤的可能性。

ios CoreData inverse relationships to itself

【讨论】:

这个建议已经由 RoboticCat 和 Alexander Kostiev 提出。在我的帖子的“编辑”中,我解释了为什么这不起作用。 @maxedison 我不认为我的建议与您在编辑中提到的相同...但很难进一步口头解释。如果你能给出你的链和步骤实体的确切例子,我认为讨论会更容易。【参考方案5】:

我的粗略想法是,您需要创建一个 NSData 类型的属性,该属性反序列化为一堆 NSManagedObjectID 或更多数组的 NSArray,以实现您所说的多维数组结构。 诚然,这是一个快速的想法,并且必然会遇到边缘情况和其他问题,这些问题是让工具 (CoreData) 做一些它本来没有做的事情。

¡祝你好运! :)

【讨论】:

【参考方案6】:

在这种情况下,我建议您使用以下架构。

StepArray 是一个抽象实体。

根据您的问题陈述,一个 Chain 可以有 Step 对象的二维数组。一个 Step 可以在多个 Chain 对象中。

我的解决方案-> 您希望 step 关系同时保存单个 Step 对象和 Step 对象数组。

我不是像二维数组那样存储,而是将单个存储对象存储为步骤,将步骤数组存储为 stepArrays。两者都是多对多关系。

Chain 对象与 Step 对象具有多对多关系。因此,这意味着您可以向其中添加尽可能多的单独步骤对象。并且由于 step 对象可以在多个 Chain 对象中,因此我们使用了从 Step 到 Chain 的链的多关系。

一个 StepArray 对象与 Step Object 有一个名为 steps 的多对多关系。所以,那些在数组中的 Step 对象,我希望它们将它存储在 StepArray 中,并带有 step 关系。

任何一个 StepArray 都是唯一的,那么我们就有一个名为 chain 和 Chain Entity 的一对一关系。

【讨论】:

在这个实现中没有办法保持Chain中对象的顺序【参考方案7】:

在我看来,链可以包含步骤和其他链(假设链只是步骤的集合)。这样说公平吗?

如果是这样,那么您可以将共享抽象实体作为父实体,然后将链定义为与抽象实体具有多对多关系,因此可以是步骤或链(包含步骤)。

当然,该模型是否有效可能取决于您需要如何与数据交互。

【讨论】:

【参考方案8】:

创建三个实体:Chain &lt;--&gt;&gt; StepInChain --&gt;&gt; Step

然后向 StepInChain 添加一个自反对多关系(即父子StepInChain &lt;--&gt;&gt; StepInChain)。您将使用这种自反关系来表示链中步骤的嵌套数组。为了保持步骤的顺序,您还需要StepInChain 中的整数索引属性。

现在一个步骤可以以任意顺序和任意深度出现在多个链中。

请注意,我省略了从 StepStepInChain 的反向关系,以强调它是如何与链的表示分离的。

【讨论】:

以上是关于序列化包含对核心数据中 NSManagedObjects 的引用的多维数组的主要内容,如果未能解决你的问题,请参考以下文章

核心数据模型对象保存的最佳实践

所有实例属性的核心数据总和

如何使用 RestKit 序列化多态对多核心数据关系?

NSManagedObjectContext,声明在哪里?

nsobject 与 nsmanagedobject 的优缺点

对许多人的核心数据谓词