在我的Lion应用中,我有这样的数据模型:

Item内的关系子项是有序的。

Xcode 4.1 (build 4B110)为我创建了文件Item.h, Item.h。m, SubItem.h和SubItem.h。

下面是Item.h的内容(自动生成):

#import <Foundation/Foundation.h>

#import <CoreData/CoreData.h>

@class SubItem;

@interface Item : NSManagedObject {
@private
}

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSOrderedSet *subitems;
@end

@interface Item (CoreDataGeneratedAccessors)

- (void)insertObject:(SubItem *)value inSubitemsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromSubitemsAtIndex:(NSUInteger)idx;
- (void)insertSubitems:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removeSubitemsAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInSubitemsAtIndex:(NSUInteger)idx withObject:(SubItem *)value;
- (void)replaceSubitemsAtIndexes:(NSIndexSet *)indexes withSubitems:(NSArray *)values;
- (void)addSubitemsObject:(SubItem *)value;
- (void)removeSubitemsObject:(SubItem *)value;
- (void)addSubitems:(NSOrderedSet *)values;
- (void)removeSubitems:(NSOrderedSet *)values;

@end

下面是Item.m的内容(自动生成):

#import "Item.h"
#import "SubItem.h"

@implementation Item

@dynamic name;
@dynamic subitems;

@end

如您所见,Item类提供了一个名为addSubitemsObject:的方法。不幸的是,当你试图以这种方式使用它时:

Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:self.managedObjectContext];
item.name = @"FirstItem";

SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:@"SubItem" inManagedObjectContext:self.managedObjectContext];

[item addSubitemsObject:subItem];

出现以下错误:

2011-09-12 10:28:45.236 Test[2002:707] *** -[NSSet intersectsSet:]: set argument is not an NSSet

你能帮我吗?

更新:

在我的错误报告刚刚过去1787天之后,今天(2016年8月1日),苹果公司给我写了这样的话:“请用最新的iOS 10测试版验证这个问题,并在bugreport.apple.com上更新你的错误报告。”让我们希望这是正确的时间:)


当前回答

我也遇到过同样的问题,但只是在我尝试了一些与我一直在做的不同的事情时。我无法看到subItem的代码,但我将假设它具有到item的反向链接。让我们称之为崇敬链接,“parentItem”,那么最简单的解决方案是:

Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:self.managedObjectContext];
item.name = @"FirstItem";

SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:@"SubItem" inManagedObjectContext:self.managedObjectContext];

//[item addSubitemsObject:subItem];
subItem.parentItem = item;

结果是它使用了苹果自己的代码,而且简单干净。此外,集合被自动添加到,并且所有的观察者都被更新。没有问题。

其他回答

就我个人而言,我刚刚用@Stephan在另一个解决方案中概述的方法直接调用替换了对CoreData生成方法的调用:

NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:@"subitems"];
      [tempSet addObject:value];
[tempSet addObject:value];

这消除了对类别的需求,当错误修复后,这些类别可能与苹果的解决方案和生成的代码发生冲突。

这有一个额外的好处,就是它是官方的方式!

是的,这绝对是一个核心数据错误。我写了一个基于objc runtime的修复程序,但当时我认为它很快就会修复。不管怎样,没有这样的运气,所以我把它贴在GitHub上KCOrderedAccessorFix。在所有实体上解决这个问题:

[managedObjectModel kc_generateOrderedSetAccessors];

特别是一个实体:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

或者只针对一段关系:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];

我已经找到窃听器了。它发生在willChangeValueForKey:withSetMutation:usingObjects:中。

这个呼叫会触发一连串的通知,可能很难追踪,当然,一个回复者的变化可能会影响到另一个,我怀疑这就是为什么苹果什么都没做的原因。

然而,这在Set中是可以的,只有OrderedSet上的Set操作会发生故障。这意味着只有四个方法需要修改。因此,我所做的只是将Set操作转换为等效的Array操作。这些工作非常完美,开销最小(但必要)。

On a critical level, this solution does suffer from one critical flaw; if you are adding objects and one of the objects already exists, then it is either not added or moved to the back of the ordered list (I don't know which). In either case, the expected ordered index of the object by the time we arrive at didChange is different from what was anticipated. This may break some people's apps, but it doesn't affect mine, since I am only ever adding new objects or I confirm their final locations before I add them.

- (void)addChildrenObject:(BAFinancialItem *)value {
    if ([self.children containsObject:value]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:self.children.count];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] addObject:value];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)removeChildrenObject:(BAFinancialItem *)value {
    if (![self.children containsObject:value]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:[self.children indexOfObject:value]];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] removeObject:value];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)addChildren:(NSOrderedSet *)values {
    if ([values isSubsetOfOrderedSet:self.children]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)removeChildren:(NSOrderedSet *)values {
    if (![self.children intersectsOrderedSet:values]) {
        return;
    }
    NSIndexSet * indexSet = [self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
        return [values containsObject:obj];
    }];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}

当然,还有一个更简单的解决方案。内容如下;

- (void)addChildrenObject:(BAFinancialItem *)value {
    if ([self.children containsObject:value]) {
        return;
    }
    [self insertObject:value inChildrenAtIndex:self.children.count];
}

- (void)removeChildrenObject:(BAFinancialItem *)value {
    if (![self.children containsObject:value]) {
        return;
    }
    [self removeObjectFromChildrenAtIndex:[self.children indexOfObject:value]];
}

- (void)addChildren:(NSOrderedSet *)values {
    if ([values isSubsetOfOrderedSet:self.children]) {
        return;
    }
    [self insertChildren:values atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)]];
}

- (void)removeChildren:(NSOrderedSet *)values {
    if (![self.children intersectsOrderedSet:values]) {
        return;
    }
    [self removeChildrenAtIndexes:[self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
        return [values containsObject:obj];
    }]];
}

我认为大家都忽略了真正的问题。它不是在访问器方法中,而是在NSOrderedSet不是NSSet的子类这一事实中。所以当-interSectsSet:以有序集作为参数调用时,它会失败。

NSOrderedSet* setA = [NSOrderedSet orderedSetWithObjects:@"A",@"B",@"C",nil];
NSSet* setB = [NSSet setWithObjects:@"C",@"D", nil];

 [setB intersectsSet:setA];

***错误-[NSSet intersectsSet:]: set参数不是NSSet

修复方法似乎是更改set操作符的实现,以便它们透明地处理类型。没有理由-intersectsSet:既适用于有序集合也适用于无序集合。

在更改通知中发生异常。大概是在处理逆关系的代码中。因为只有当我设置一个反比关系时才会发生。

下面的方法对我很有效

@implementation MF_NSOrderedSetFixes

+ (void) fixSetMethods
{
    NSArray* classes = [NSArray arrayWithObjects:@"NSSet", @"NSMutableSet", @"NSOrderedSet", @"NSMutableOrderedSet",nil];

    [classes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSString* name = obj;
        Class aClass = objc_lookUpClass([name UTF8String]);
        [MF_NSOrderedSetFixes fixMethodWithSetArgument:@selector(intersectsSet:) forClass:aClass];
        [MF_NSOrderedSetFixes fixMethodWithSetArgument:@selector(isSubsetOfSet:) forClass:aClass];
    }];
}

typedef BOOL (*BoolNSetIMP)(id _s,SEL sel, NSSet*);

/*
    Works for all methods of type - (BOOL) method:(NSSet*) aSet
*/
+ (void) fixMethodWithSetArgument:(SEL) aSel forClass:(Class) aClass 
{
    /* Check that class actually implements method first */
    /* can't use get_classInstanceMethod() since it checks superclass */
    unsigned int count,i;
    Method method = NULL;
    Method* methods = class_copyMethodList(aClass, &count);
    if(methods) {
        for(i=0;i<count;i++) {
            if(method_getName(methods[i])==aSel) {
                method = methods[i];
            }
        }
        free(methods);
    }
    if(!method) {
        return;
    }

   // Get old implementation
   BoolNSetIMP originalImp  = (BoolNSetIMP) method_getImplementation(method);
   IMP newImp = imp_implementationWithBlock(^BOOL(NSSet *_s, NSSet *otherSet) {
        if([otherSet isKindOfClass:[NSOrderedSet class]]) {
            otherSet = [(NSOrderedSet*)otherSet set];
        }
        // Call original implementation
        return originalImp(_s,aSel,otherSet);
    });
    method_setImplementation(method, newImp);
}
@end

我同意这里可能有漏洞。我已经修改了添加对象>setter的实现,以正确地追加到NSMutableOrderedSet。 - (void)addSubitemsObject:(SubItem *)value { NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems]; [tempSet addObject:价值); 自我。subitems = tempSet; } 将集合重新分配给self。子项将确保发送will /DidChangeValue通知>。

leell,你确定在这样的自定义设置NSMutableOrderedSet值存储在该集将被CoreData正确地保存到数据库吗?我没有检查,但它看起来像CoreData对NSOrderedSet一无所知,并期望NSSet作为多关系容器。