Exception levée dans les accesseurs générés par NSOrderedSet

364

Sur mon application Lion, j'ai ce modèle de données:

entrez la description de l'image ici

La relation à l' subitemsintérieur Item est ordonnée .

Xcode 4.1 (build 4B110) m'a créé pour le fichier Item.h, Item.m, SubItem.het SubItem.h.

Voici le contenu (généré automatiquement) de 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

Et voici le contenu (généré automatiquement) de Item.m:

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

@implementation Item

@dynamic name;
@dynamic subitems;

@end

Comme vous pouvez le voir, la classe Itempropose une méthode appelée addSubitemsObject:. Malheureusement, lorsque vous essayez de l'utiliser de cette façon:

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

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

[item addSubitemsObject:subItem];

cette erreur apparaît:

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

Pouvez-vous m'aider?

Mise à jour:

Après seulement 1 787 jours après mon rapport de bogue, Apple (1 août 2016) m'a écrit ceci: "Veuillez vérifier ce problème avec la dernière version bêta d'iOS 10 et mettre à jour votre rapport de bogue sur bugreport.apple.com avec vos résultats." . Espérons que ce soit le bon moment :)

Dev
la source
5
Je vois le même problème. Espérons que cela sera bientôt corrigé. Bien que l'utilisation directe de l'ensemble ordonné mutable soit une solution de contournement facile pour le moment. Remarque: j'utilise mogenerator, mais je suppose qu'il utilise le même générateur Apple en interne pour cette partie du code généré.
Chad Podoski
12
Cela fait presque 2 ans! Le réparerez-vous dans iOS 7, Apple? —— Je veux juste partager avec ceux qui se demandent si ce bug est toujours là: "Oui, ça l'est."
an0
1
Très proche de deux ans maintenant, c'est toujours un problème dans tous les aperçus des développeurs xcode 5.
Korvin Szanto
2
Voyez-vous toujours le problème si vous utilisez l'accessoire KVC approprié? (ie mutableOrderedSetValueForKey:)
quellish
3
Semble être toujours un problème sur Mavericks.
Tim

Réponses:

263

J'ai reproduit votre configuration à la fois avec votre modèle de données et la mienne avec des noms différents. J'ai eu la même erreur dans les deux cas.

Ressemble à un bogue dans le code généré automatiquement par Apple.

TechZen
la source
60
L'ID de bogue est 10114310. Il a été signalé le 13 septembre 2011 mais aujourd'hui (15 janvier 2012), il est toujours "ouvert". C'est incroyable, vu le nombre de personnes qui ont le même problème.
Dev
14
Mise à jour: aujourd'hui (11 mai 2012) le bug # 10114310 est toujours ouvert, 241 jours après mon rapport (13 septembre 2011). Incroyable.
Dev
23
Je viens d'en parler avec un ingénieur d'Apple lors d'une des sessions CoreData Lab à la WWDC. Ils reconnaissent le problème et qu'il s'agit d'un véritable bug, et d'après ce que j'ai vu, il a le statut "critique", mais bien sûr, il n'y a aucune promesse quant au moment où ils le corrigeront. Je ne pense pas que ce sera corrigé dans iOS6 / Mountain Lion. Je pense qu'il serait bon de reproduire ce radar davantage. Actuellement, il compte environ 25 dup, plus c'est mieux!
DaGaMs
40
Juste vérifié aujourd'hui, il est toujours là dans iOS 7 GM / OMG! Je ne peux pas le croire…
an0
79
Mise à jour: 797 jours, 2 nouvelles versions majeures d'iOS et d'innombrables versions de Xcode se sont écoulées depuis que j'ai corrigé le bogue # 10114310. Et c'est toujours "ouvert". Incroyable.
Dev
244

Je suis d'accord qu'il peut y avoir un bug ici. J'ai modifié l'implémentation du setter d'objets add pour l'ajouter correctement à un NSMutableOrderedSet.

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

La réaffectation de l'ensemble à self.subitems garantit l'envoi des notifications Will / DidChangeValue.

InitJason
la source
Votre extrait de code était exactement ce dont j'avais besoin pour contourner ce problème. J'espère qu'Apple résoudra le problème éventuellement, mais jusqu'à présent, je n'ai vu aucun problème avec l'utilisation de votre approche.
Christopher Hujanen
J'obtiens cette erreur lorsque j'essaye d'implémenter cette solution de contournement. en cela?
DerekH
@DerekH isEqualToSet est une méthode que seul NSSet a, donc je suppose que vous avez converti, créé ou traitez un pointeur comme un NSArray avant de revenir au NSManagedObject, qui devrait, pour une raison quelconque, appeler isEqualToOrderedSet pour déterminer si l'ensemble a besoin même changer ou être laissé tel quel.
InitJason
3
@MarkAmery Tested. Vérifié. Le setter dynamique self.subitems envoie les notifications. La solution JLust est donc correcte.
bernstein
3
C'est une bonne réponse, mais c'est inefficace. Vous copiez l'ensemble ordonné, le modifiez puis le recopiez. L'effet n'est pas seulement un coup sûr pour l'ensemble ordonné, mais des notifications sont envoyées que chaque fois que l'ensemble ordonné est modifié, son contenu entier est changé! Si cet ensemble ordonné est utilisé pour un UITable par exemple, cela pourrait avoir de sérieuses ramifications pour la mise à jour. J'ai décrit dans ma solution exactement d'où vient l'erreur et j'ai montré une méthode plus efficace pour contourner l'erreur.
Owen Godfrey
111

J'ai décidé d'améliorer la solution en implémentant toutes les méthodes requises:

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];
    }
}
Dmitry Makarenko
la source
1
Quel est le type de crash? 'removeObjectFromSubitemsAtIndex' ne supprime pas ces sous-éléments, ils existent toujours dans votre stockage, c'est juste le moyen de supprimer la relation entre les objets.
Dmitry Makarenko
2
kItemsKey est une constante qui a été ajoutée juste pour plus de commodité dans les appels de méthodes KVO. C'est un nom de relation ordonnée pour lequel vous écrivez vos méthodes.
Dmitry Makarenko
1
C'est ce que je pense que c'est. Merci. Mais mon problème est que les données ne sont pas enregistrées dans la base de données à l'aide de ces méthodes.
Bagusflyer
4
!!!!!!!!! Il suffit de copier le code et de changer les noms des méthodes, cela fonctionne parfaitement !!! Ceci est la réponse la plus rapide.
flypig
1
C'est formidable, mais la création de la copie temporaire de l'ensemble commandé n'est pas nécessaire. Le coupable est willChangeValueForKey:withSetMutation:usingObjectsce que vous avez réussi à éviter. Après cela, utilisez simplement [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]ou [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]selon le cas. Voir ma réponse pour plus de détails.
Owen Godfrey
38

Oui, c'est définitivement un bogue Core Data. J'ai écrit un correctif basé sur ObjC-Runtime il y a quelque temps, mais à l'époque, je pensais que ce serait bientôt corrigé. Quoi qu'il en soit, pas de chance, donc je l'ai posté sur GitHub en tant que KCOrderedAccessorFix . Contournez le problème sur toutes vos entités:

[managedObjectModel kc_generateOrderedSetAccessors];

Une entité en particulier:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

Ou juste pour une relation:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];
Sterling Archer
la source
Je me demande si cela va entrer en conflit avec le vrai correctif d'Apple ou non?
tia
3
Cela ne devrait pas entrer en conflit avec le correctif d'Apple, car son objectif est de remplacer l'implémentation d'Apple, quoi qu'il arrive. Quand / si cela est réellement corrigé par Apple, j'ajouterai peut-être un - (BOOL)kc_needsOrderedSetAccessorFix;ou quelque chose qui vérifie la version Foundation / iOS.
Sterling Archer
2
Il existe déjà un KCOrderedAccessorFix.podspec dans le référentiel maître CocoaPods. Donc, pour lier cela à vos projets, vous pouvez simplement ajouter "pod 'KCOrderedAccessorFix'" à votre Podfile
Anton Matosov
Cela avait quelques problèmes avec iOS 8 (signatures de méthode incorrectes pour objc_msg_send)
NSTJ
Sur iOS9 ça marche, beau travail! C'est la meilleure solution de tous les temps, pas besoin de changer quoi que ce soit dans votre code!
Borzh
32

Au lieu de faire une copie, je suggère d'utiliser l'accesseur dans NSObject pour avoir accès au NSMutableOrderedSet des relations.

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

Par exemple, les notes de publication de Core Data pour iOS v5.0 s'y réfèrent.

Dans un court test, cela a fonctionné dans mon application.

Stephan
la source
1
Impossible de refactoriser les chaînes littérales aussi facilement. Le compilateur peut taper check self.subitems si vous utilisez du code.
logancautrell
1
@logancautrell oui c'est correct. Cela dépend de la priorité du cas d'utilisation spécifique. En général, je me concentre sur la sauvegarde des ressources, en particulier dans ce cas, car ce n'était qu'une solution de contournement.
Stephan
2
La chaîne littérale peut être remplacée par NSStringFromSelector(@selector(subitems))cependant :)
Ja͢ck
17

J'ai suivi le bug. Cela se produit dans willChangeValueForKey:withSetMutation:usingObjects:.

Cet appel déclenche une chaîne de notifications qui peut être difficile à suivre, et bien sûr, les modifications apportées à un intervenant peuvent avoir des implications pour un autre, ce qui, je pense, est la raison pour laquelle Apple n'a rien fait.

Cependant, il est correct dans Set et ce sont les seules opérations Set sur un OrderedSet qui fonctionnent mal. Cela signifie qu'il n'y a que quatre méthodes qui doivent être modifiées. Par conséquent, tout ce que j'ai fait a été de convertir les opérations Set en leurs opérations Array équivalentes. Ceux-ci fonctionnent parfaitement et avec des frais généraux minimes (mais nécessaires).

À un niveau critique, cette solution souffre d'un défaut critique; si vous ajoutez des objets et que l'un des objets existe déjà, il n'est pas ajouté ou déplacé à l'arrière de la liste ordonnée (je ne sais pas lequel). Dans les deux cas, l'indice ordonné attendu de l'objet au moment où nous arrivons didChangeest différent de ce qui était prévu. Cela peut casser les applications de certaines personnes, mais cela n'affecte pas le mien, car j'ajoute uniquement de nouveaux objets ou je confirme leur emplacement final avant de les ajouter.

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

Bien sûr, il existe une solution plus simple. c'est comme suit;

- (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];
    }]];
}
Owen Godfrey
la source
Dommage que tout le monde semble avoir ignoré cette réponse, semble définitivement la meilleure solution.
George
Cette solution a des performances bien meilleures que celles qui utilisent OrderSetWithOrderedSet pour créer un ensemble local. Cela a un gros frais généraux lorsque vous avez de grands ensembles de données. La solution la plus simple semble être simplement une version refactorisée de la version initiale avec les méthodes non présentées.
David Pettigrew
1
Je vois toujours un plantage dans addChildren: *** Arrêt de l'application en raison de l'exception non capturée 'NSInvalidArgumentException', raison: '- [TrackHistory insertTrackpoints: atIndexes:]: sélecteur non reconnu envoyé à l'instance 0x1702b1b20'
Victor Bogdan
@OwenGodfrey Pour la solution la plus simple, où implémentez-vous ces méthodes? Je reçois une exception: [Parent insertObject: inChildrenAtIndex:] sélecteur non reconnu envoyé à l'instance 0x6180000ac480.
Dalmazio
votre variable est "Parent" avec un "P" majuscule? Cela signifie-t-il que vous appelez la classe "Parent" ou avez-vous une variable d'instance nommée "Parent"? Si ma classe est Parent, alors j'ai implémenté ces méthodes au bas de Parent, mais vous devrez l'appeler sur une instance, qui serait plus probablement nommée "parent" avec un "p" en minuscule, car ce ne sont pas des méthodes de classe .
Owen Godfrey
10

Le document Apple To Many Relations dit: vous devez accéder à l'ensemble mutable proxy ou à l'ensemble ordonné en utilisant

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

La modification de cet ensemble ajoutera ou supprimera des relations avec votre objet géré. Accès à l'ensemble ordonné mutable à l'aide de l'accesseur avec [] ou. la notation est erronée et échouera.

Nicolas Manzini
la source
3
Pour être honnête, les documents disent également: "ou l'une des méthodes de mutation de relation générées automatiquement (voir Méthodes d'
Matt
D'accord, d'accord ... vous avez raison. Eh bien, disons que c'est la façon de travailler la plus simple ...
Nicolas Manzini
9

Reçu la même erreur, la solution @LeeIII a fonctionné pour moi (merci!). Je propose de le modifier légèrement:

  • utiliser la catégorie objective-c pour stocker la nouvelle méthode (donc nous ne perdrons pas notre méthode si l'élément est généré à nouveau)
  • vérifier si nous avons déjà un ensemble mutable

Contenu de 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
Danik
la source
Bon point pour déplacer ce code dans la catégorie. Mais nous devons toujours accepter les ajouts / suppressions réels avec les appels will / setPrimitiveValue / didChange comme dans la réponse @Dmitry Makarenko.
Vladimir Shutyuk
8

Si vous utilisez mogenerator, alors au lieu de

[parentObject add<Child>sObject:childObject];

utilisez simplement:

[[parent object <child>sSet] addObject:childObject];
Καrτhικ
la source
Parce que mogenerator prend en charge le code supplémentaire que vous auriez autrement à écrire et autorise simplement l'accès à l'objet d'ensemble sous-jacent.
Καrτhικ
Il semble qu'un correctif vient d'être validé, ce qui signifie que mogenerator générera des corps corrigés ... github.com/dmakarenko/mogenerator/commit/…
combinatoire
1
J'utilise mogeneratormais j'ai toujours le bug.
Colas
7

Personnellement, je viens de remplacer les appels aux méthodes générées par CoreData par des appels directs à la méthode, comme indiqué dans une autre solution de @Stephan:

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

Cela supprime le besoin de catégories qui pourraient ultérieurement entrer en conflit avec une solution d'Apple au code généré lorsque le bogue est corrigé.

Cela a l'avantage supplémentaire d'être la façon officielle de le faire!

Grouchal
la source
Cela donne l'erreur suivante: '[<CLASS 0x20886d10> valueForUndefinedKey:]: cette classe n'est pas conforme au codage des valeurs de clé pour les sous-éléments de clé.'
jmstone617
Bien que cela ne m'énerve toujours pas que cela ne soit pas répertorié dans les problèmes connus d'Apple (j'ai ouvert un radar pour le geste apparemment futile qu'il est), cette solution a parfaitement fonctionné pour moi.
Scott Corscadden
J'aimerais avoir vu cette réponse plus tôt; J'utilisais la réponse ayant obtenu le vote le plus élevé jusqu'à ce que j'aie récemment creusé et finalement mis en œuvre exactement ce que vous avez ici :)
Ja͢ck
Pourquoi est addObject:appelé deux fois?
Jason Moore
5

Il semble que si vous liez le parent à l'enfant en définissant le parent sur l'enfant et non l'inverse, cela fonctionne sans se bloquer.

Donc si vous le faites:

[child setParent:parent]

au lieu de

[parent setChildObects:child]

Cela devrait fonctionner, au moins cela fonctionne sur iOS 7 et n'a eu aucun problème avec la relation.

Cata
la source
1
Ne fait pas beaucoup de bien lorsque les deux parties sont trop nombreuses. Ensuite, il n'y a pas de relation parent-enfant claire.
fatuhoku
3

J'ai eu le même problème, mais seulement quand j'ai essayé quelque chose de différent de ce que je faisais. Je ne peux pas voir le code de subItem, mais je suppose qu'il a un lien inverse vers l'élément. Appelons ce lien vénéré, "parentItem", alors la solution la plus simple est la suivante:

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;

L'effet est qu'il utilise le propre code d'Apple et qu'il est simple et propre. De plus, l'ensemble est automatiquement ajouté et tous les observateurs sont mis à jour. Aucun problème.

Owen Godfrey
la source
C'est très gentil. Il résout tout le problème et le maintient dans l'ordre. Encore fou que le bug soit toujours présent. Un autre avantage de cette réponse est que si vous régénérez vos modèles de données de base, vous n'avez pas à réécrire vos corrections de bogues. Merci!
Johan S
Voir mon autre réponse. J'ai suivi le bug plus en détail. C'est toujours le moyen le plus simple, mais l'autre méthode est la meilleure car elle ouvre plus de possibilités.
Owen Godfrey
Hou la la! Finalement!!! Merci! (A essayé votre autre code, mais a obtenu des erreurs, quelque chose à propos de ce type incorrect a été envoyé à [self didChange: NSKeyValueChangeInsertion valuesAtIndexes: indexSet forKey: ChildrenKey];)
Leonard Pauli
3

Je suis juste tombé sous le coup de ce problème et l'ai résolu en utilisant une implémentation beaucoup plus simple que les autres décrites ici. J'utilise simplement les méthodes disponibles surNSManagedObject pour gérer les relations lorsque vous n'utilisez pas de sous-classes.

Un exemple d'implémentation pour insérer une entité dans une NSOrderedSetrelation ressemblerait à ceci:

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

Cela fonctionne parfaitement et c'est ce que j'utilisais avant de passer aux NSManagedObjectsous-classes.

Mic Pringle
la source
3

Ce problème m'est apparu lors de la migration d'un projet d'Objective-C vers Swift 2 avec XCode 7 . Ce projet fonctionnait, et pour une bonne raison: j'utilisais MOGenerator qui avait des méthodes de remplacement pour corriger ce bogue. Mais toutes les méthodes ne nécessitent pas de remplacement.

Voici donc la solution complète avec un exemple de classe, en s'appuyant autant que possible sur les accesseurs par défaut.

Disons que nous avons une liste avec les articles commandés

D'abord une victoire rapide si vous avez une relation un à plusieurs, le plus simple est de simplement faire:

item.list = list

au lieu de

list.addItemsObject(item)

Maintenant, si ce n'est pas une option , voici ce que vous pouvez faire:

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

Bien sûr, si vous utilisez Objective-C, vous pouvez faire exactement la même chose puisque c'est là que j'ai eu l'idée en premier :)

Nycen
la source
3

Je suis d'accord qu'il y a peut-être un bug ici. J'ai modifié l'implémentation de l'objet add> setter pour l'ajouter correctement à un NSMutableOrderedSet.

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

La réaffectation de l'ensemble à self.subitems garantit l'envoi des notifications Will / DidChangeValue>.

Leelll, êtes-vous sûr qu'après une telle configuration personnalisée des valeurs NSMutableOrderedSet stockées dans cet ensemble seront correctement enregistrées dans la base de données par CoreData? Je n'ai pas vérifié cela, mais il semble que CoreData ne sache rien de NSOrderedSet et attend NSSet comme conteneur de relation to-many.

DisableR
la source
Pour que CoreData renvoie ou prenne un objet NSOrderedSet, plusieurs conditions doivent être remplies comme l'a montré cette question lancée. Les erreurs les plus courantes que je constate lorsque les personnes qui partagent mon code sont un développeur qui n'exécute pas Lion. Le framework NSOrderedSets n'est pas disponible sur snowleopard. Mais oui, je n'ai pas vu cela échouer, même si je ne suis pas sûr que ce soit le meilleur en termes de performances. Je suppose que cela prend l'ensemble et le remplace au lieu d'insérer simplement l'enregistrement souhaité.
InitJason
2

Je pense que tout le monde manque le vrai problème. Ce n'est pas dans les méthodes d'accesseur mais plutôt dans le fait qu'il NSOrderedSetne s'agit pas d'une sous-classe de NSSet. Donc, quand -interSectsSet:est appelé avec un ensemble ordonné comme argument, il échoue.

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

 [setB intersectsSet:setA];

échoue avec *** -[NSSet intersectsSet:]: set argument is not an NSSet

Il semble que le correctif consiste à modifier l'implémentation des opérateurs d'ensemble afin qu'ils gèrent les types de manière transparente. Aucune raison pour laquelle un-intersectsSet: devrait fonctionner avec un ensemble ordonné ou non ordonné.

L'exception se produit dans la notification de modification. Vraisemblablement dans le code qui gère la relation inverse. Puisque cela ne se produit que si je mets une relation inverse.

Ce qui suit a fait l'affaire pour moi

@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
Entropie
la source
2

Je viens de recevoir le problème dans Swift (Xcode 6.1.1).

La réponse était NE PAS CODER DE MÉTHODE OU DE CHOSES SUPPLÉMENTAIRES CHOSE dans vos sous-classes NSManagedObject. Je pense que c'est une erreur de compilation. Bug très étrange ..

J'espère que cela aide ..

lobodart
la source
3
Donc, si je ne peux pas implémenter les autres correctifs, que dois-je faire pour résoudre ce problème?
Ben Leggiero
2

J'ai résolu ce problème en définissant l'inverse sur No Inverse, je ne sais pas pourquoi, il y a peut-être Apple Bug.entrez la description de l'image ici

LevinYan
la source
1

J'ai la même situation avec un élément appelé "signaux" au lieu de "sous-éléments". La solution avec tempset fonctionne dans mes tests. De plus, j'ai eu un problème avec la méthode removeSignals :. Cette dérogation semble fonctionner:

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

S'il existe une meilleure façon de procéder, veuillez me le faire savoir. Mes valeurs ne sont jamais plus de 10 à 20 éléments, donc les performances ne sont pas très préoccupantes - néanmoins, veuillez signaler tout ce qui est pertinent.

Merci,

Damien

Damien Del Russo
la source
1

J'ai trouvé cette question en recherchant le message d'erreur sur Google, et je voulais juste souligner que j'ai rencontré cette erreur d'une manière légèrement différente (sans utiliser d'ensembles ordonnés). Ce n'est pas tout à fait une réponse à la question donnée, mais je la poste ici juste au cas où elle serait utile à toute autre personne qui tombe sur cette question lors de la recherche.

J'ajoutais une nouvelle version de modèle, ajoutais des relations aux modèles existants et définissais moi-même les méthodes add * Object dans le fichier d'en-tête. Lorsque j'ai essayé de les appeler, j'ai eu l'erreur ci-dessus.

Après avoir examiné mes modèles, j'ai réalisé que j'avais bêtement oublié de cocher la case "Relation à plusieurs".

Donc, si vous rencontrez cela et que vous n'utilisez pas d'ensembles ordonnés, vérifiez votre modèle.

BenV
la source
1

J'ai trouvé un correctif pour ce bug qui fonctionne pour moi. Je remplace juste ceci:

[item addSubitemsObject:subItem];

avec ça:

item.subitemsObject = subItem;
Bimawa
la source
1

Meilleure version de la bonne réponse dans SWIFT

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

tempSet.add(newItem)
parent!.subItems = tempSet
emreoktem
la source
0

J'ai trouvé que l'utilisation de la méthode par LeeIII fonctionnait, mais sur le profilage, j'ai trouvé qu'elle était drastiquement lente. Il a fallu 15 secondes pour analyser 1000 éléments. Commenter le code pour ajouter la relation est passé de 15 secondes à 2 secondes.

Ma solution de contournement (qui est plus rapide mais beaucoup plus laide) consiste à créer un tableau mutable temporaire, puis à copier dans l'ensemble ordonné lorsque toute l'analyse est terminée. (ce n'est qu'une victoire de performance si vous allez ajouter de nombreuses relations).

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

J'espère que cela aide quelqu'un d'autre!

Robert
la source
0

Robert,

Je suis d'accord que votre réponse fonctionnera pour cela, mais gardez à l'esprit qu'il existe déjà une méthode créée automatiquement pour ajouter un ensemble de valeurs à une relation. La documentation d'Apple ( comme on le voit ici dans la section "Relations To-many" ou ici dans la section "Méthodes d'accesseur de relation Custom To-Many") les implémente de cette façon:

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

Vous pouvez facilement compiler votre ensemble de relations en dehors des données de base, puis les ajouter toutes à la fois en utilisant cette méthode. C'est peut-être moins moche que la méthode que vous avez suggérée;)

JiuJitsuCoder
la source