在我的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
我在用XCode 7将一个项目从Objective-C迁移到Swift 2时遇到了这个问题。这个项目过去是有效的,而且有一个很好的理由:我使用MOGenerator,它有替换方法来修复这个错误。但并不是所有的方法都需要替换。
这里有一个完整的解决方案和一个示例类,尽可能地依赖默认访问器。
假设我们有一个包含有序项的List
首先,如果你有一对多的关系,最简单的方法就是:
item.list = list
而不是
list.addItemsObject(item)
现在,如果这不是一个选择,以下是你可以做的:
// Extension created from your DataModel by selecting it and
// clicking on "Editor > Create NSManagedObject subclass…"
extension List {
@NSManaged var items: NSOrderedSet?
}
class List
// Those two methods work out of the box for free, relying on
// Core Data's KVC accessors, you just have to declare them
// See release note 17583057 https://developer.apple.com/library/prerelease/tvos/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc7_release_notes.html
@NSManaged func removeItemsObject(item: Item)
@NSManaged func removeItems(items: NSOrderedSet)
// The following two methods usually work too, but not for NSOrderedSet
// @NSManaged func addItemsObject(item: Item)
// @NSManaged func addItems(items: NSOrderedSet)
// So we'll replace them with theses
// A mutable computed property
var itemsSet: NSMutableOrderedSet {
willAccessValueForKey("items")
let result = mutableOrderedSetValueForKey("items")
didAccessValueForKey("items")
return result
}
func addItemsObject(value: Item) {
itemsSet.addObject(value)
}
func addItems(value: NSOrderedSet) {
itemsSet.unionOrderedSet(value)
}
end
当然,如果你使用的是Objective-C,你可以做完全相同的事情,因为这是我最初得到的想法:)