Виняток, закинутий в покоління NSOrderedSet


364

У моєму додатку Lion у мене є така модель даних:

введіть тут опис зображення

Взаємовідносини subitemsвсередині Item впорядковані .

Xcode 4.1 (збірка 4B110) створив для мене файл Item.h, Item.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 днів із мого звіту про помилки, сьогодні (1 серпня 2016 р.) Apple написав мені це: "Будь ласка, підтвердьте цю проблему за допомогою останньої версії бета-версії iOS 10 та оновіть свій звіт про помилку на bugreport.apple.com своїми результатами". . Будемо сподіватися, що це саме час :)


5
Я бачу ту саму проблему. Сподіваємось, це скоро виправиться. Хоча використання впорядкованого набору, що змінюється безпосередньо, на даний момент є простим вирішенням проблеми. Примітка: я використовую генератор, але я припускаю, що він використовує той же генератор Apple внутрішньо для цієї частини згенерованого коду.
Чад Подоськи

12
Це майже 2 роки! Ви виправите це в iOS 7, Apple? —— Мені просто хочеться поділитися з тими, хто цікавиться, чи існує ця помилка: „Так, є”.
an0

1
Вже близько двох років, це все ще проблема у всіх попередніх переглядах розробників xcode 5.
Корвін Сзанто

2
Ви все ще бачите проблему, якщо використовуєте відповідний аксесуар KVC? (тобто mutableOrderedSetValueForKey:)
чудовий

3
Як видається, проблема Mavericks все ще залишається проблемою.
Тім

Відповіді:


263

Я відтворив ваші настройки як з вашою моделлю даних, так і з моєю власною з різними іменами. Я отримав однакову помилку в обох випадках.

Виглядає як помилка в автогенерованому коді Apple.


60
Ідентифікатор помилки - 10114310. Повідомлялося про нього 13 вересня 2011 року, але сьогодні (15 січня-2012) він все ще "відкритий". Це неймовірно, враховуючи кількість людей, які мають однакові проблеми.
Дев

14
Оновлення: сьогодні (11 травня 2012 року) помилка №10114310 все ще відкрита через 241 день після мого звіту (13 вересня 2011 р.). Неймовірно.
Дев

23
Я щойно підняв це з інженером Apple під час однієї із сесій CoreData Lab в WWDC. Вони визнають проблему і що це справжня помилка, і з того, що я бачив, вона має "критичний" статус, але, звичайно, немає обіцянок, коли вони її виправлять. Я не думаю, що це буде виправлено в iOS6 / Mountain Lion. Я думаю, було б добре дублювати цей радар далі. Наразі в ньому близько 25 дублів, чим більше, тим краще!
DaGaMs

40
Щойно перевірено сьогодні, він все ще є в iOS 7 GM / OMG! Я не можу повірити…
an0

79
Оновлення: 797 днів, 2 нові основні версії iOS і незліченна кількість версій Xcode пройшли з моменту заповнення помилки # 10114310. І це все ще "відкрито". Неймовірно.
Dev

244

Я згоден, що тут може виникнути помилка. Я змінив реалізацію додатка об'єкта add, щоб правильно додавати його до NSMutableOrderedSet.

- (void)addSubitemsObject:(SubItem *)value {
    NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
    [tempSet addObject:value];
    self.subitems = tempSet;
}

Перепризначення набору для self.subitems забезпечить надсилання сповіщень Will / DidChangeValue.


Ваш фрагмент коду був саме тим, що мені потрібно було обійти проблемою. Сподіваюся, що Apple вирішує цю проблему врешті-решт, але поки що я не бачив жодних проблем із використанням вашого підходу.
Крістофер Худжанен

Я отримую цю помилку, коли я намагаюся реалізувати цю роботу. в це?
ДерекH

@DerekH isEqualToSet - це метод, який має тільки NSSet, тому, на вашу думку, ви перетворили, створили або трактуєте вказівник як NSArray перед тим, як повернутися до NSManagedObject, який повинен, за яких-небудь причин викликати ISEqualToOrderedSet, щоб визначити, чи потрібен набір навіть змінитися або залишитись так, як є.
InitJason

3
@MarkAmery Тестовано. Перевірено. Динамічний сетер self.subitems дійсно надсилає сповіщення. Тож рішення JLust правильне.
Бернштейн

3
Це хороша відповідь, але її неефективна. Ви копіюєте весь замовлений набір, модифікуєте його, а потім копіюєте його назад. Ефект - це не просто потрапляння до упорядкованого набору, але надсилаються повідомлення про те, що щоразу, коли замовлений набір змінюється, весь його зміст змінюється! Якщо, наприклад, цей упорядкований набір використовується для UITable, це може мати серйозні наслідки для оновлення. Я окреслив у своєму рішенні саме те, звідки походить помилка, і я показав більш ефективний метод обходу помилки.
Оуен Годфрі

111

Я вирішив вдосконалити рішення, застосувавши всі необхідні методи:

static NSString *const kItemsKey = @"<#property#>";

- (void)insertObject:(<#Type#> *)value in<#Property#>AtIndex:(NSUInteger)idx {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet insertObject:value atIndex:idx];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)removeObjectFrom<#Property#>AtIndex:(NSUInteger)idx {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet removeObjectAtIndex:idx];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)insert<#Property#>:(NSArray *)values atIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet insertObjects:values atIndexes:indexes];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)remove<#Property#>AtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet removeObjectsAtIndexes:indexes];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)replaceObjectIn<#Property#>AtIndex:(NSUInteger)idx withObject:(<#Type#> *)value {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet replaceObjectAtIndex:idx withObject:value];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)replace<#Property#>AtIndexes:(NSIndexSet *)indexes with<#Property#>:(NSArray *)values {
    [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet replaceObjectsAtIndexes:indexes withObjects:values];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)add<#Property#>Object:(<#Type#> *)value {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSUInteger idx = [tmpOrderedSet count];
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    [tmpOrderedSet addObject:value];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)remove<#Property#>Object:(<#Type#> *)value {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSUInteger idx = [tmpOrderedSet indexOfObject:value];
    if (idx != NSNotFound) {
        NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet removeObject:value];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    }
}

- (void)add<#Property#>:(NSOrderedSet *)values {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
    NSUInteger valuesCount = [values count];
    NSUInteger objectsCount = [tmpOrderedSet count];
    for (NSUInteger i = 0; i < valuesCount; ++i) {
        [indexes addIndex:(objectsCount + i)];
    }
    if (valuesCount > 0) {
        [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet addObjectsFromArray:[values array]];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    }
}

- (void)remove<#Property#>:(NSOrderedSet *)values {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
    for (id value in values) {
        NSUInteger idx = [tmpOrderedSet indexOfObject:value];
        if (idx != NSNotFound) {
            [indexes addIndex:idx];
        }
    }
    if ([indexes count] > 0) {
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet removeObjectsAtIndexes:indexes];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    }
}

1
Що таке тип аварії? 'removeObjectFromSubitemsAtIndex' не видаляє ці підпункти, вони все ще існують у вашому сховищі, це лише спосіб усунення взаємозв'язку між об'єктами.
Дмитро Макаренко

2
kItemsKey - це константа, яка додається просто для зручності у викликах методів KVO. Це ім'я впорядкованих відносин, для яких ви пишете свої методи.
Дмитро Макаренко

1
Це я думаю, що це так. Дякую. Але моя проблема полягає в тому, що дані не зберігаються в базі даних за допомогою цих методів.
Bagusflyer

4
!!!!!!!!! Просто скопіювавши код та змінивши назви методів, він прекрасно працює !!! Це найшвидша відповідь.
мухомор

1
Це приголомшливо, але створювати тимчасову копію замовленого набору не потрібно. Винуватець willChangeValueForKey:withSetMutation:usingObjects, якого ви успішно уникли. Після цього просто використовуйте [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]або, [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]як годиться. Детальну інформацію див. У моїй відповіді.
Оуен Годфрі

38

Так, це, безумовно, помилка Core Data. Я записав виправлення на основі ObjC-Runtime на деякий час назад, але в той час я зрозумів, що це буде виправлено незабаром. У всякому разі, такої удачі немає, тож я розмістив її на GitHub як KCOrderedAccessorFix . Вирішіть проблему для всіх ваших організацій:

[managedObjectModel kc_generateOrderedSetAccessors];

Зокрема, одна організація:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

Або просто для одного відносини:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];

Цікаво, чи це буде конфліктувати з реальним виправленням від Apple чи ні?
тіа

3
Це не повинно суперечити виправленню Apple, оскільки його мета - перекрити впровадження Apple, незважаючи ні на що. Коли / якщо це фактично виправлено Apple, можливо я додам - (BOOL)kc_needsOrderedSetAccessorFix;або щось, що перевіряє версію Foundation / iOS.
Стерлінг Арчер

2
У головній репортажі CocoaPods вже є KCOrderedAccessorFix.podspec. Тож для того, щоб пов’язати це з вашими проектами, ви можете просто додати "pod" KCOrderedAccessorFix "" у свій Podfile
Антон Матосов,

У цьому виникли проблеми з iOS 8 (неправильні підписи методу для objc_msg_send)
NSTJ

На iOS9 це працює, приємна робота! Це найкраще рішення коли-небудь, не потрібно нічого змінювати у своєму коді!
Борж

32

Замість того, щоб зробити копію, я пропоную використовувати аксесуар в NSObject, щоб отримати доступ до NSMutableOrderedSet відносин.

- (void)addSubitemsObject:(SubItem *)value {
      NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:@"subitems"];
      [tempSet addObject:value];
 }

наприклад, Примітки до випуску основних даних для iOS v5.0 посилаються на це.

У короткому тесті він працював у моїй заявці.


1
Неможливо легко перетворити буквальні рядки. Компілятор може ввести перевірку self.subitems, якщо ви використовуєте код.
logancautrell

1
@logancautrell так, це правильно. Це залежить від пріоритету конкретного випадку використання. Взагалі я зосереджуюсь на економії ресурсів, особливо в цьому випадку, тому що це був лише спосіб вирішення.
Стефан

2
Буквальний рядок може бути замінений NSStringFromSelector(@selector(subitems))хоч :)
Ja͢ck

17

Я відслідковував помилку. Це відбувається в willChangeValueForKey:withSetMutation:usingObjects:.

Цей виклик заповнює ланцюжок сповіщень, які можуть бути важкими для відстеження, і, звичайно, зміни одного респондента можуть мати наслідки для іншого, і я підозрюю, що тому Apple нічого не зробила.

Однак у програмі Set і його єдиних операціях Set на OrdersSet це нормально. Це означає, що існує лише чотири методи, які потрібно змінити. Тому я лише перетворив операції Set на їх еквівалентні операції масиву. Вони працюють ідеально і мінімально (але необхідно) накладні витрати.

На критичному рівні це рішення страждає від однієї критичної вади; якщо ви додаєте об'єкти, а один із об’єктів вже існує, він або не додається, ні переміщується на зворотній бік упорядкованого списку (я не знаю, який). В будь-якому випадку очікуваний впорядкований індекс об'єкта до моменту, коли ми приїдемо didChange, відрізняється від очікуваного. Це може зламати додатки для деяких людей, але це не вплине на моє, оскільки я лише коли-небудь додаю нові об'єкти або підтверджую їх кінцеві місця, перш ніж додати їх.

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

Шкода, що всі інші, здається, не помітили цю відповідь, безумовно, здаються найкращим рішенням.
Джордж

Це рішення має набагато кращі показники, ніж ті, що використовують orderSetWithOrderedSet для створення локального набору. Це великі накладні витрати, коли у вас є великі набори даних. Здається, що простішим рішенням є лише реконструйована версія початкової версії методами, які не показані.
Девід Петтігрю

1
Я все ще бачу збій у addChildren: *** Запуск програми через невиконаний виняток "NSInvalidArgumentException", причина: '- [TrackHistory insertTrackpoints: atIndexes:]: нерозпізнаний селектор, надісланий до екземпляра 0x1702b1b20'
Віктор Богдан

@OwenGodfrey Для більш простого рішення, де ви реалізуєте ці методи? Я отримую виняток: [Parent insertObject: inChildrenAtIndex:] нерозпізнаний селектор, надісланий екземпляру 0x6180000ac480.
Далмазіо

ваша змінна - "Батько" з великою літерою "Р"? Це означає, що ви називаєте клас "Parent", або у вас є змінна примірник з назвою "Parent"? Якщо мій клас - це Parent, я реалізував ці методи в нижній частині Parent, але вам потрібно було б викликати його в екземплярі, який, швидше за все, буде названий "батьківським" з малого "p", оскільки це не методи класу .
Оуен Годфрі

10

В документи яблуку Багато відносин говорить: ви повинні отримати доступ до проксі - змінюваний набір або упорядкований набір з використанням

NSMutableOrderedSet * set = [managedObject mutableOrderedSetValueForKey:@"toManyRelation"];

Змінення цього набору додасть або видалить відносини до вашого керованого об’єкта. Доступ до упорядкованого набору, що змінюється, за допомогою аксесуара, будь то [] або. Позначення неправильне і буде невдалим.


3
Справедливості, документи також говорять: "або один із автоматично сформованих методів мутації відносин (див. Методи динамічно-генерованих аксесуарів):"
Мт

Гаразд гаразд ... ти маєш рацію. Ну, скажімо, що це найпростіший робочий шлях ...
Ніколя Манзіні

9

Отримавши ту саму помилку, рішення @LeeIII працювало на мене (спасибі!). Я пропоную трохи змінити його:

  • використовуйте категорію aim-c для зберігання нового методу (тому ми не будемо втрачати наш метод, якщо Item знову генерується)
  • перевірте, чи є у нас набір змін

Вміст 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

Хороший момент перенести цей код у категорію. Але все ж нам потрібно прийняти фактичне додавання / видалення за допомогою волі / setPrimitiveValue / didChange викликів, як у відповіді @Dmitry Makarenko.
Володимир Шутюк

8

Якщо ви використовуєте mogenerator, то замість цього

[parentObject add<Child>sObject:childObject];

просто використовуйте:

[[parent object <child>sSet] addObject:childObject];

Оскільки mogenerator піклується про додатковий код, то в іншому випадку вам доведеться писати і просто дозволяє отримати доступ до базового об'єкта.
Καrτhικ

Схоже, що виправлена ​​помилка, що означає, що генератор генерує виправлені тіла ... github.com/dmakarenko/mogenerator/commit/…
комбінатор

1
Я використовую, mogeneratorале у мене все ще є помилка.
Колас

7

Особисто я щойно замінив дзвінки на генеровані CoreData методи прямими викликами до методу, як викладено в іншому рішенні від @Stephan:

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

Це усуває потребу в категоріях, які згодом можуть конфліктувати з рішенням Apple від створеного коду, коли помилка виправлена.

Це має ще один плюс - офіційний спосіб зробити це!


Це дає таку помилку: '[<CLASS 0x20886d10> valueForUndefinedKey:]: цей клас не відповідає ключовому значенню кодування для ключових підпунктів.'
jmstone617

Хоча це все ще не дратує мене, що це не вказано у відомих питаннях Apple (я відкрив радар для очевидно марного жесту, який він є), це рішення спрацювало для мене бездоганно.
Скотт Корскадден

Бажаю, я раніше бачив цю відповідь; Я використовував найвищу відповідь, поки нещодавно не робив копання і нарешті реалізував саме те, що у вас є тут :)
Ja͢ck

Чому addObject:дзвонять двічі?
Джейсон Мур

5

Здається, що якщо ви зв’яжете батька з дитиною, встановивши батька на дитину, а не навпаки, це працює без збоїв.

Отже, якщо ви робите:

[child setParent:parent]

замість

[parent setChildObects:child]

Він повинен працювати, принаймні, він працює на iOS 7 і не мав жодних проблем із відносинами.


1
Не дає нічого хорошого, коли обидві сторони мають багато. Тоді немає чітких стосунків батько-дитина.
фатухоку

3

У мене була така ж проблема, але лише тоді, коли я спробував щось інше від того, що робив. Я не бачу коду для підрозділу, але я вважаю, що він має зворотне посилання на елемент. Давайте назвемо це посилання на реверанс "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;

Ефект полягає в тому, що він використовує власний код яблука, і він простий і чистий. Крім того, набір автоматично додається до, а всі спостерігачі оновлюються. Нема проблем.


Це дуже приємно. Це вирішує всю проблему і залишає її упорядкованою. Досі божевільно, що помилка все ще присутня. Ще одна перевага цієї відповіді полягає в тому, що якщо ви регенеруєте основні моделі даних, вам не доведеться переписувати свої виправлення помилок. Дякую!
Йохан S

Дивіться іншу мою відповідь. Я відстежив помилку більш детально. Це все ще найпростіший спосіб, але інший метод є найкращим, оскільки він відкриває більше можливостей.
Оуен Годфрі

Оце Так! Нарешті !!! Дякую! (Спробував ваш інший код, але отримав помилки. Щось про той неправильний тип було надіслано на адресу [self didChange: NSKeyValueChangeInsertion valuesAtIndexes: indexSet forKey: ChildrenKey];)
Леонард Паулі

3

Я просто помилився з цим питанням і вирішив його за допомогою набагато простішої реалізації, ніж інші, викладені тут. Я просто використовую методи, доступні NSManagedObjectдля роботи з відносинами, коли не використовую підкласи.

Приклад реалізації для вставки суб'єкта у NSOrderedSetвідносини виглядатиме так:

- (void)addAddress:(Address *)address
{
    if ([self.addresses containsObject:address]) {
        return;
    }
    // Use NSManagedObject's methods for inserting an object
    [[self mutableOrderedSetValueForKey:@"addresses"] addObject:address];
}

Це прекрасно працює, і це те, що я використовував до переходу до NSManagedObjectпідкласів.


3

Ця проблема у мене виникла під час міграції проекту з Objective-C на Swift 2 за допомогою XCode 7 . Цей проект працював і з поважної причини: я використовував MOGenerator, який мав методи заміни, щоб виправити цю помилку. Але не всі методи потребують заміни.

Тож ось повне рішення з прикладом класу, максимально покладаючись на аксесуари за замовчуванням.

Скажімо, у нас є Список із замовленими предметами

По-перше, швидка перемога, якщо у вас є стосунки один до багатьох, найпростіше це зробити:

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, ви можете зробити саме те саме, оскільки саме тут я вперше отримав ідею :)


3

Я згоден, що тут може бути помилка. Я змінив реалізацію об’єкта add> setter, щоб правильно додати NSMutableOrderedSet.

- (void)addSubitemsObject:(SubItem *)value {
     NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
     [tempSet addObject:value];
     self.subitems = tempSet;
}

Перепризначення набору для self.subitems забезпечить надсилання повідомлень Will / DidChangeValue>.

Leelll, ти впевнений, що після такого настроюваного налаштування значень NSMutableOrderedSet, що зберігаються у цьому наборі, буде правильно збережено CoreData до бази даних? Я цього не перевіряв, але схоже, що CoreData нічого не знає про NSOrderedSet і очікує, що NSSet є контейнером відносин для багатьох.


Щоб CoreData повернув або прийняв об'єкт NSOrderedSet, необхідно виконати декілька умов, як показало це розпочате питання. Найбільш поширені помилки, які я бачу, коли люди, що ділилися моїм кодом, були розробниками, які не працюють з Lion. Рамка NSOrderedSets недоступна на snowleopard. Але так, я не бачив цього невдачі, хоча не впевнений, що це найкраще в роботі. Я думаю, що це займає весь набір і замінює його, а не просто вставляти потрібний запис.
InitJason

2

Я думаю, що всім не вистачає реальної проблеми. Це не в методах accessor, а в тому, що NSOrderedSetне є підкласом NSSet. Отже, коли -interSectsSet:викликається з упорядкованим набором як аргумент, він не працює.

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

 [setB intersectsSet:setA];

не вдається з *** -[NSSet intersectsSet:]: set argument is not an NSSet

Виглядає, що виправлення полягає в тому, щоб змінити реалізацію заданих операторів, щоб вони обробляли типи прозоро. Немає причини, чому а-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

2

Щойно я отримав проблему в Swift (Xcode 6.1.1).

Відповідь: НЕ КОДУЙТЕ ЯКИЙ МЕТОД АБО ДОПОМОГА кодуйте жодних методів речей у ваших підкласах NSManagedObject. Я думаю, що це помилка компілятора. Дуже дивна помилка ..

Сподіваюся, це допомагає ..


3
Отже, якщо я не можу реалізувати інші виправлення, що мені робити, щоб це виправити?
Ben Leggiero

2

Я вирішив цю проблему, встановивши інверсію на No Inverse, не знаю чому, можливо, існує Apple Bug.введіть тут опис зображення


1

У мене така ж ситуація з елементом, який називається "сигнали" замість "підпунктів". Рішення з tempset працює в моєму тестуванні. Крім того, у мене виникла проблема з методом removeSignals:. Схоже, це переопрацювання працює:

- (void)removeSignals:(NSOrderedSet *)values {
    NSMutableOrderedSet* tempset = [NSMutableOrderedSet orderedSetWithOrderedSet:self.signals];
    for (Signal* aSignal in values) {
        [tempset removeObject:aSignal];
    }
    self.signals = tempset;
}

Якщо є кращий спосіб зробити це, будь ласка, повідомте мене про це. Мої значення введення ніколи не перевищують 10-20 елементів, тому продуктивність не викликає особливих проблем - тим не менше, будь ласка, вкажіть будь-що релевантне.

Дякую,

Дамієн


1

Я знайшов це запитання, гуглившись на повідомлення про помилку, і просто хотів зазначити, що я зіткнувся з цією помилкою дещо по-іншому (не використовуючи впорядковані набори). Це не зовсім відповідь на дане запитання, але я публікую його тут на всякий випадок, якщо це корисно для всіх, хто натрапляє на це питання під час пошуку.

Я додав нову версію моделі та додав деякі відносини до існуючих моделей і сам визначив методи add * Object у файлі заголовка. Коли я намагався зателефонувати їм, я отримав помилку вище.

Переглянувши свої моделі, я зрозумів, що я тупо забув встановити прапорець "Для багатьох".

Тож якщо ви стикаєтеся з цим і не використовуєте впорядковані набори, перевірте свою модель.


1

Я знайшов виправлення цієї помилки, яка працює для мене. Я просто замінюю це:

[item addSubitemsObject:subItem];

з цим:

item.subitemsObject = subItem;

1

Краща версія правильної відповіді в SWIFT

var tempSet = NSMutableOrderedSet()
if parent!.subItems != nil {
    tempSet = NSMutableOrderedSet(orderedSet: parent!.subItems!)
}

tempSet.add(newItem)
parent!.subItems = tempSet

0

Я виявив, що за допомогою методу ЛіІІІ працював, але при профілюванні виявив, що він різко повільний. Щоб проаналізувати 1000 предметів, знадобилося 15 секунд. Коментуючи код, щоб додати відносини, обернулося 15 секунд на 2 секунди.

Моє вирішення (яке швидше, але набагато потворніше) передбачає створення тимчасового змінного масиву, а потім копіювання в упорядкований набір, коли буде виконано весь аналіз. (це лише виграш від ефективності, якщо ви збираєтеся додати багато стосунків).

@property (nonatomic, retain) NSMutableArray* tempItems;
 ....
@synthesize tempItems = _tempItems;
 ....

- (void) addItemsObject:(KDItem *)value 
{
    if (!_tempItems) {
        self.tempItems = [NSMutableArray arrayWithCapacity:500];
    }
    [_tempItems addObject:value];
}

// Call this when you have added all the relationships
- (void) commitRelationships 
{
    if (_tempItems) {
        self.items = [NSOrderedSet orderedSetWithArray:self.tempItems];
        self.tempItems = nil;
    }
}

Я сподіваюся, що це допоможе комусь іншому!


0

Роберт,

Я згоден, ваша відповідь спрацює для цього, але майте на увазі, що існує автоматично створений метод для додавання цілого набору значень до відносин. Документація Apple ( як показано тут у розділі "Стосунки для багатьох" або тут у розділі "Користувацькі способи доступу до багатьох відносин") реалізує їх таким чином:

- (void)addEmployees:(NSSet *)value
{
[self willChangeValueForKey:@"employees"
      withSetMutation:NSKeyValueUnionSetMutation
      usingObjects:value];
[[self primitiveEmployees] unionSet:value];
[self didChangeValueForKey:@"employees"
      withSetMutation:NSKeyValueUnionSetMutation
      usingObjects:value];
}

- (void)removeEmployees:(NSSet *)value
{
[self willChangeValueForKey:@"employees"
      withSetMutation:NSKeyValueMinusSetMutation
      usingObjects:value];
[[self primitiveEmployees] minusSet:value];
[self didChangeValueForKey:@"employees"
      withSetMutation:NSKeyValueMinusSetMutation
      usingObjects:value];
}

Ви можете легко скласти набір відносин поза основними даними, а потім додати їх усі відразу за допомогою цього методу. Це може бути менш потворно, ніж запропонований вами метод;)


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.