Exception thrown in NSOrderedSet generated accessors

后端 未结 25 2285
天涯浪人
天涯浪人 2020-11-22 09:07

On my Lion app, I have this data model:

\"enter

The relationship subitem

相关标签:
25条回答
  • 2020-11-22 09:32

    I reproduced your setup both with your data model and one of my own with different names. I got the same error in both cases.

    Looks like a bug in Apple's autogenerated code.

    0 讨论(0)
  • 2020-11-22 09:33

    This issue occurred to me while migrating a project from Objective-C to Swift 2 with XCode 7. That project used to work, and for a good reason: I was using MOGenerator which had replacement methods to fix this bug. But not all methods require a replacement.

    So here's the complete solution with an example class, relying on default accessors as much as possible.

    Let's say we have a List with ordered Items

    First a quick win if you have a one/to-many relationship, the easiest is to just do:

    item.list = list
    

    instead of

    list.addItemsObject(item)
    

    Now, if that's not an option, here's what you can do:

    // 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
    

    Of course, if you're using Objective-C, you can do the exact same thing since this is where I got the idea in the first place :)

    0 讨论(0)
  • 2020-11-22 09:33

    I solved this problem by set the inverse to No Inverse, I don't know why, Maybe there is Apple Bug.

    0 讨论(0)
  • 2020-11-22 09:33

    I found this question by googling for the error message, and just wanted to point out that I ran into this error in a slightly different way (not using ordered sets). This isn't quite an answer to the given question, but I'm posting it here just in case it is helpful to anyone else who stumbles across this question while searching.

    I was adding a new model version, and added some relationships to existing models, and defined the add*Object methods in the header file myself. When I tried to call them, I got the error above.

    After reviewing my models, I realized I had stupidly forgotten to check the "To-Many Relationship" checkbox.

    So if you're running into this and you're not using ordered sets, double check your model.

    0 讨论(0)
  • 2020-11-22 09:34

    I've tracked the bug. It occurs in willChangeValueForKey:withSetMutation:usingObjects:.

    This call sets off a chain of notifications which may be difficult to track, and of course changes to one responder may have implications for another, which I suspect is why Apple have done nothing.

    However, it is okay in Set and its only the Set operations on an OrderedSet that malfunction. That means there are only four methods that need to be altered. Therefore, all I did was convert the Set operations to their equivalent Array operations. These work perfectly and minimal (but necessary) overheads.

    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];
    }
    

    Of course, there is an easier solution. it is as follows;

    - (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];
        }]];
    }
    
    0 讨论(0)
  • 2020-11-22 09:34

    Received the same error, @LeeIII solution worked for me (thanks!). I suggest slightly modify it:

    • use objective-c category to store the new method (so we wont lose our method if Item is generated again)
    • check if we already have mutable set

    Content of Item+category.m:

    #import "Item+category.h"
    
    @implementation Item (category)
    
    - (void)addSubitemsObject:(SubItem *)value {
        if ([self.subitems isKindOfClass:[NSMutableOrderedSet class]]) {
            [(NSMutableOrderedSet *)self.subitems addObject:value];
        } else {
            NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
            [tempSet addObject:value];
            self.subitems = tempSet;
        }
    }
    
    @end
    
    0 讨论(0)
提交回复
热议问题