在我的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上更新你的错误报告。”让我们希望这是正确的时间:)
我已经找到窃听器了。它发生在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