Eccezione generata negli accessori generati da NSOrderedSet


364

Sulla mia app Lion, ho questo modello di dati:

inserisci qui la descrizione dell'immagine

La relazione subitemsall'interno Item è ordinata .

Xcode 4.1 (accumulo 4B110) ha creato per me il file Item.h, Item.m, SubItem.he SubItem.h.

Ecco il contenuto (autogenerato) di 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

Ed ecco il contenuto (autogenerato) di Item.m:

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

@implementation Item

@dynamic name;
@dynamic subitems;

@end

Come puoi vedere, la classe Itemoffre un metodo chiamato addSubitemsObject:. Sfortunatamente, quando provi ad usarlo in questo modo:

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

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

[item addSubitemsObject:subItem];

appare questo errore:

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

Mi potete aiutare?

Aggiornare:

Dopo solo 1.787 giorni dalla mia segnalazione di bug, oggi (1 agosto 2016) Apple mi ha scritto questo: "Per favore verifica questo problema con l'ultima build di iOS 10 beta e aggiorna la tua segnalazione di bug su bugreport.apple.com con i tuoi risultati." . Speriamo che sia il momento giusto :)


5
Sto riscontrando lo stesso problema. Spero che venga risolto presto. Sebbene l'utilizzo del set ordinato mutabile direttamente sia una soluzione alternativa per il momento. Nota: sto usando mogenerator, ma suppongo che stia usando lo stesso generatore Apple internamente per questa porzione del codice generato.
Chad Podoski,

12
Sono passati quasi 2 anni! Lo riparerai su iOS 7, Apple? —— Voglio solo condividere con quelli che si chiedono se questo bug è ancora lì: "Sì, lo è."
an0

1
Molto vicino a due anni ormai, questo è ancora un problema in tutte le anteprime degli sviluppatori di xcode 5.
Korvin Szanto,

2
Riscontri ancora il problema se usi l'accessorio KVC appropriato? (ad es. mutableOrderedSetValueForKey:)
quellish

3
Sembra essere ancora un problema su Mavericks.
Tim

Risposte:


263

Ho riprodotto la tua configurazione sia con il tuo modello di dati sia con una mia con nomi diversi. Ho avuto lo stesso errore in entrambi i casi.

Sembra un bug nel codice generato automaticamente di Apple.


60
L'ID bug è 10114310. È stato segnalato il 13 settembre 2011 ma oggi (15 gennaio 2012) è ancora "aperto". È incredibile, considerando il numero di persone che hanno lo stesso problema.
Dev

14
Aggiornamento: oggi (11 maggio 2012) il bug # 10114310 è ancora aperto, 241 giorni dopo la mia segnalazione (13 settembre 2011). Incredibile.
Dev

23
Ho appena parlato di questo con un ingegnere Apple durante una delle sessioni del CoreData Lab al WWDC. Riconoscono il problema e che si tratta di un vero bug, e da quello che ho visto ha lo stato "critico", ma ovviamente non c'è promessa su quando lo risolveranno. Non penso che verrà risolto in iOS6 / Mountain Lion. Penso che sarebbe bene duplicare ulteriormente questo radar. Attualmente ha circa 25 dup, più meglio è!
DaGaMs

40
Appena controllato oggi, è ancora lì in iOS 7 GM / OMG! Non ci posso credere ...
an0

79
Aggiornamento: 797 giorni, 2 nuove versioni principali di iOS e innumerevoli versioni Xcode sono passate da quando ho riempito il bug # 10114310. Ed è ancora "aperto". Incredibile.
Dev

244

Sono d'accordo che qui potrebbe esserci un bug. Ho modificato l'implementazione del settatore oggetti add per aggiungerlo correttamente a un NSMutableOrderedSet.

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

La riassegnazione del set a self.subitems garantirà l'invio delle notifiche Will / DidChangeValue.


Lo snippet di codice era proprio quello di cui avevo bisogno per risolvere questo problema. Spero che Apple risolva il problema alla fine, ma finora non ho riscontrato alcun problema con l'utilizzo del tuo approccio.
Christopher Hujanen,

Ricevo questo errore quando provo a implementare questa soluzione alternativa. [__NSArrayI isEqualToSet:]: selettore non riconosciuto inviato all'istanza ... Questo di solito proviene da un elemento che è stato rilasciato, ma non riesce a trovare dove, nessuno esegue in questo?
DerekH,

@DerekH isEqualToSet è un metodo che solo NSSet ha, quindi la mia ipotesi è che tu abbia convertito, creato o stia trattando un puntatore come un NSArray prima di passare indietro a NSManagedObject, che dovrebbe, se un motivo qualsiasi, chiamare isEqualToOrderedSet per determinare se il set ha bisogno per cambiare o essere lasciato così com'è.
InitJason

3
@MarkAmery Tested. Verificato. Il setter dinamico self.subitems invia le notifiche. Quindi la soluzione JLust è corretta.
Bernstein

3
Questa è una buona risposta, ma è inefficiente. Copi l'intero set ordinato, lo modifichi e poi lo copi indietro. L'effetto non è solo un successo per il set ordinato, ma vengono inviate notifiche che ogni volta che il set ordinato viene modificato, il suo intero contenuto viene modificato! Se, ad esempio, questo set ordinato viene utilizzato per un UITable, ciò potrebbe avere serie conseguenze per l'aggiornamento. Nella mia soluzione ho delineato esattamente l'origine dell'errore e ho mostrato un metodo più efficiente per aggirare l'errore.
Owen Godfrey,

111

Ho deciso di migliorare la soluzione implementando tutti i metodi richiesti:

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
Qual è il tipo di incidente? 'removeObjectFromSubitemsAtIndex' non elimina questi elementi secondari, esistono ancora nella memoria, è solo il modo di rimuovere la relazione tra gli oggetti.
Dmitry Makarenko,

2
kItemsKey è una costante che è stata aggiunta solo per comodità nelle chiamate ai metodi KVO. È un nome della relazione ordinata per cui stai scrivendo i tuoi metodi.
Dmitry Makarenko,

1
Questo è quello che penso che sia. Grazie. Ma il mio problema è che i dati non vengono salvati nel database utilizzando questi metodi.
Bagusflyer,

4
!!!!!!!!! Basta copiare il codice e cambiare i nomi dei metodi, funziona perfettamente !!! Questa è la risposta più veloce
flypig,

1
È fantastico, ma non è necessario creare la copia temporanea del set ordinato. Il colpevole è willChangeValueForKey:withSetMutation:usingObjectsche hai evitato con successo. Successivamente, basta usare [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]o [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]come appropriato. Vedi la mia risposta per i dettagli.
Owen Godfrey,

38

Sì, questo è sicuramente un bug di dati di base. Ho scritto una correzione basata su ObjC-Runtime qualche tempo fa, ma al momento ho pensato che sarebbe stato risolto presto. Comunque, nessuna fortuna, quindi l'ho pubblicato su GitHub come KCOrderedAccessorFix . Risolvi il problema su tutte le entità:

[managedObjectModel kc_generateOrderedSetAccessors];

Un'entità in particolare:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

O solo per una relazione:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];

Mi chiedo se questo sarà in conflitto con la vera soluzione di Apple o no?
tia,

3
Questo non dovrebbe essere in conflitto con la correzione di Apple, poiché il suo scopo è quello di sovrascrivere l'implementazione di Apple, qualunque cosa accada. Quando / se questo è effettivamente risolto da Apple, forse aggiungerò una - (BOOL)kc_needsOrderedSetAccessorFix;o qualcosa che controlla la versione di Foundation / iOS.
Sterling Archer

2
Esiste già un KCOrderedAccessorFix.podspec nel repository master CocoaPods. Quindi, per collegare questo ai tuoi progetti, puoi semplicemente aggiungere "pod 'KCOrderedAccessorFix'" al tuo Podfile
Anton Matosov,

Ciò presentava alcuni problemi con iOS 8 (firme del metodo errate per objc_msg_send)
NSTJ

Su iOS9 funziona, bel lavoro! Questa è la migliore soluzione di sempre, non c'è bisogno di cambiare nulla nel tuo codice!
Borzh,

32

Invece di fare una copia suggerisco di usare l'accessor in NSObject per ottenere l'accesso a NSMutableOrderedSet delle relazioni.

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

ad es. le note di rilascio dei dati principali per iOS v5.0 fanno riferimento a questo.

In un breve test ha funzionato nella mia applicazione.


1
Impossibile refactoring stringhe letterali con la stessa facilità. Il compilatore può digitare check self.subitems se si utilizza il codice.
logancautrell,

1
@logancautrell sì, questo è corretto. Dipende dalla priorità del caso d'uso specifico. In generale, mi concentro sul risparmio di risorse, specialmente in questo caso perché questa era solo una soluzione alternativa.
Stephan,

2
La stringa letterale può essere sostituita da NSStringFromSelector(@selector(subitems)):)
Ja͢ck

17

Ho rintracciato il bug. Si verifica in willChangeValueForKey:withSetMutation:usingObjects:.

Questa chiamata fa scattare una catena di notifiche che possono essere difficili da tracciare, e ovviamente le modifiche a un risponditore possono avere implicazioni per un altro, il che sospetto sia il motivo per cui Apple non ha fatto nulla.

Tuttavia, va bene in Set e sono solo le operazioni Set su un OrderedSet che funzionano male. Ciò significa che ci sono solo quattro metodi che devono essere modificati. Pertanto, tutto ciò che ho fatto è stato convertire le operazioni Set in operazioni Array equivalenti. Questi funzionano perfettamente e minimizzano le spese generali (ma necessarie).

A livello critico, questa soluzione presenta un difetto critico; se stai aggiungendo oggetti e uno degli oggetti esiste già, allora non viene aggiunto o spostato sul retro dell'elenco ordinato (non so quale). In entrambi i casi, l'indice ordinato previsto dell'oggetto al momento in cui arriviamo didChangeè diverso da quello previsto. Ciò può interrompere le app di alcune persone, ma non influisce sulle mie, poiché aggiungo sempre nuovi oggetti o confermo la loro posizione finale prima di aggiungerli.

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

Certo, c'è una soluzione più semplice. è il seguente;

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

Peccato che tutti gli altri abbiano trascurato questa risposta, sembra sicuramente la soluzione migliore.
George,

Questa soluzione offre prestazioni molto migliori rispetto a quelle che utilizzano orderSetWithOrderedSet per creare un set locale. Questo ha un grande sovraccarico quando si hanno grandi set di dati. La soluzione più semplice sembra essere solo una versione refactored di quella iniziale con i metodi non mostrati.
David Pettigrew,

1
Sto ancora vedendo un arresto anomalo in addChildren: *** Terminare l'app a causa di un'eccezione non rilevata 'NSInvalidArgumentException', motivo: '- [TrackHistory insertTrackpoint: atIndexes:]: selettore non riconosciuto inviato all'istanza 0x1702b1b20'
Victor Bogdan

@OwenGodfrey Per la soluzione più semplice, dove stai implementando questi metodi? Ricevo un'eccezione: [Parent insertObject: inChildrenAtIndex:] selettore non riconosciuto inviato all'istanza 0x6180000ac480.
Dalmazio,

la tua variabile è "Parent" con la "P" maiuscola? Ciò significa che sei chiamata la classe "Parent" o hai una variabile di istanza denominata "Parent"? Se la mia classe è Parent, allora ho implementato questi metodi nella parte inferiore di Parent, ma avresti bisogno di chiamarlo su un'istanza, che molto probabilmente sarebbe chiamata "parent" con una "p" minuscola, poiché questi non sono metodi di classe .
Owen Godfrey,

10

La documentazione di Apple To Many Relations dice: dovresti accedere al set mutabile proxy o al set ordinato usando

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

La modifica di questo set aggiungerà o rimuoverà le relazioni all'oggetto gestito. Accedere al set ordinato mutabile utilizzando l'accessor sia con [] o. la notazione è sbagliata e fallirà.


3
Per essere onesti, i documenti dicono anche: "o uno dei metodi di mutatore delle relazioni generati automaticamente (vedi Metodi di accesso generati dinamicamente):"
Matt

Va bene va bene ... hai ragione. Bene, diciamo che è il modo di lavorare più semplice ...
Nicolas Manzini,

9

Ricevuto lo stesso errore, la soluzione @LeeIII ha funzionato per me (grazie!). Suggerisco di modificarlo leggermente:

  • usa la categoria obiettivo-c per memorizzare il nuovo metodo (quindi non perderemo il nostro metodo se l'articolo viene generato di nuovo)
  • controlla se abbiamo già un set mutabile

Contenuto di 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

Buon punto per spostare questo codice nella categoria. Ma dobbiamo ancora abbracciare le effettive operazioni di aggiunta / rimozione con le chiamate will / setPrimitiveValue / didChange come nella risposta di @Dmitry Makarenko.
Vladimir Shutyuk,

8

Se stai usando mogenerator, allora invece di

[parentObject add<Child>sObject:childObject];

usa semplicemente:

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

Poiché mogenerator si occupa del codice aggiuntivo che altrimenti dovresti scrivere e consente semplicemente l'accesso all'oggetto set sottostante.
Καrτhικ,

Sembra che sia stata appena impegnata una correzione che significa che il mogeneratore genererà corpi corretti ... github.com/dmakarenko/mogenerator/commit/…
combinatorio

1
Sto usando mogeneratorma ho ancora il bug.
Colas,

7

Personalmente ho appena sostituito le chiamate ai metodi generati da CoreData con chiamate dirette al metodo come indicato in un'altra soluzione di @Stephan:

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

Ciò elimina la necessità di categorie che potrebbero in seguito essere in conflitto con una soluzione da Apple al codice generato quando il bug viene risolto.

Questo ha il vantaggio di essere il modo ufficiale per farlo!


Questo dà il seguente errore: '[[<CLASS 0x20886d10> valueForUndefinedKey:]: questa classe non è conforme alla codifica del valore-chiave per i sotto-elementi chiave.'
jmstone617,

Anche se mi dà ancora fastidio che questo non sia elencato nei problemi noti di Apple (ho aperto un radar per il gesto apparentemente inutile che è), questa soluzione ha funzionato perfettamente per me.
Scott Corscadden,

Vorrei aver visto questa risposta prima; Stavo usando la risposta più votata fino a poco tempo fa ho scavato e finalmente implementato esattamente quello che hai qui :)
Ja͢ck

Perché viene addObject:chiamato due volte?
Jason Moore

5

Sembra che se colleghi il genitore al figlio impostando il genitore sul figlio e non viceversa, funziona senza crash.

Quindi se lo fai:

[child setParent:parent]

invece di

[parent setChildObects:child]

Dovrebbe funzionare, almeno funziona su iOS 7 e non ha avuto problemi con la relazione.


1
Non fa molto bene quando entrambe le parti sono troppe. Quindi non esiste una chiara relazione genitore-figlio.
Fatuhoku,

3

Ho avuto lo stesso problema, ma solo quando ho provato qualcosa di diverso da quello che stavo facendo. Non riesco a vedere il codice per subItem, ma suppongo che abbia un collegamento inverso all'elemento. Chiamiamo questo reveres link "parentItem", quindi la soluzione più semplice è questa:

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'effetto è che utilizza il codice di Apple ed è semplice e pulito. Inoltre, il set viene aggiunto automaticamente a e tutti gli osservatori vengono aggiornati. Nessun problema.


Questo è molto carino. Risolve l'intero problema e lo mantiene ordinato. Ancora pazzo che il bug sia ancora presente. Un altro vantaggio di questa risposta è che se si rigenerano i modelli di dati core non è necessario riscrivere le correzioni dei bug. Grazie!
Johan S,

Vedi la mia altra risposta. Ho rintracciato il bug in modo più dettagliato. Questo è ancora il modo più semplice, ma l'altro metodo è il migliore perché apre più possibilità.
Owen Godfrey,

Wow! Finalmente!!! Grazie! (Ho provato l'altro tuo codice, ma ho ricevuto errori, qualcosa su quel tipo sbagliato è stato inviato a [self didChange: NSKeyValueChangeInsertion valoriAtIndexes: indexSet forKey: ChildrenKey];)
Leonard Pauli

3

Ho appena sbagliato questo problema e l'ho risolto utilizzando un'implementazione molto più semplice rispetto agli altri descritti qui. Uso semplicemente i metodi disponibili suNSManagedObject per gestire le relazioni quando non si usano le sottoclassi.

Un'implementazione di esempio per l'inserimento di un'entità in una NSOrderedSetrelazione sarebbe simile alla seguente:

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

Funziona perfettamente ed è quello che stavo usando prima di passare alle NSManagedObjectsottoclassi.


3

Questo problema si è verificato durante la migrazione di un progetto da Objective-C a Swift 2 con XCode 7 . Quel progetto funzionava, e per una buona ragione: stavo usando MOGenerator che aveva metodi di sostituzione per correggere questo errore. Ma non tutti i metodi richiedono una sostituzione.

Quindi, ecco la soluzione completa con una classe di esempio, basandosi il più possibile sugli accessori predefiniti.

Diciamo che abbiamo un elenco con gli articoli ordinati

Innanzitutto una rapida vittoria se hai una relazione uno / a molti, il più semplice è semplicemente fare:

item.list = list

invece di

list.addItemsObject(item)

Ora, se questa non è un'opzione , ecco cosa puoi fare:

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

Ovviamente, se stai usando Objective-C, puoi fare esattamente la stessa cosa poiché è qui che ho avuto l'idea in primo luogo :)


3

Sono d'accordo che forse c'è un bug qui. Ho modificato l'implementazione dell'oggetto add> setter per aggiungerlo correttamente a un NSMutableOrderedSet.

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

La riassegnazione del set a self.subitems garantirà l'invio delle notifiche Will / DidChangeValue>.

Leelll, sei sicuro che dopo tale installazione personalizzata dei valori NSMutableOrderedSet memorizzati in quel set verranno salvati correttamente nel database da CoreData? Non l'ho verificato, ma sembra che CoreData non sappia nulla di NSOrderedSet e si aspetta che NSSet sia un contenitore di relazioni per molti.


Affinché CoreData ritorni o prenda un oggetto NSOrderedSet, devono essere soddisfatte più condizioni come mostrato dalla domanda iniziale. Gli errori più comuni che vedo quando le persone che condividono il mio codice sono stati uno sviluppatore che non esegue Lion. Il framework NSOrderedSets non è disponibile su snowleopard. Ma sì, non ho visto questo errore, anche se non sono sicuro che questo sia il migliore in termini di prestazioni. Immagino che questo prenda l'intero set e lo sostituisca invece di inserire semplicemente il record desiderato.
InitJason

2

Penso che a tutti manchi il vero problema. Non è nei metodi di accesso ma piuttosto nel fatto che NSOrderedSetnon è una sottoclasse di NSSet. Quindi, quando -interSectsSet:viene chiamato con un set ordinato come argomento, fallisce.

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

 [setB intersectsSet:setA];

fallisce con *** -[NSSet intersectsSet:]: set argument is not an NSSet

Sembra che la correzione sia modificare l'implementazione degli operatori set in modo che gestiscano i tipi in modo trasparente. Nessun motivo per cui a-intersectsSet: dovrebbe lavorare con un set ordinato o non ordinato.

L'eccezione si verifica nella notifica di modifica. Presumibilmente nel codice che gestisce la relazione inversa. Dal momento che succede solo se ho impostato una relazione inversa.

Quanto segue ha fatto il trucco per me

@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

Ho appena avuto il problema in Swift (Xcode 6.1.1).

La risposta è stata: NON CODIFICARE NESSUN METODO O COSE AGGIUNTIVE nelle sottoclassi di NSManagedObject. Penso che sia un errore del compilatore. Insetto molto strano ..

Spero che sia d'aiuto ..


3
Quindi, se non riesco ad implementare le altre correzioni, cosa devo fare per risolvere questo problema?
Ben Leggiero,

2

Ho risolto questo problema impostando l'inverso su No Inverse, non so perché, forse c'è Apple Bug.inserisci qui la descrizione dell'immagine


1

Ho la stessa situazione con un elemento chiamato "segnali" anziché "elementi secondari". La soluzione con tempset funziona nei miei test. Inoltre, ho riscontrato un problema con il metodo removeSignals:. Questa sostituzione sembra funzionare:

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

Se esiste un modo migliore per farlo, per favore fatemelo sapere. I miei valori immessi non superano mai i 10-20 articoli, quindi le prestazioni non destano grande preoccupazione, tuttavia si prega di indicare qualcosa di rilevante.

Grazie,

Damien


1

Ho trovato questa domanda cercando su Google il messaggio di errore e volevo solo sottolineare che ho riscontrato questo errore in un modo leggermente diverso (non usando i set ordinati). Questa non è una risposta alla domanda data, ma la sto pubblicando qui nel caso in cui sia utile a chiunque si imbatta in questa domanda durante la ricerca.

Stavo aggiungendo una nuova versione del modello, ho aggiunto alcune relazioni ai modelli esistenti e ho definito personalmente i metodi add * Object nel file di intestazione. Quando ho provato a chiamarli, ho ricevuto l'errore sopra.

Dopo aver esaminato i miei modelli, mi sono reso conto che mi ero stupidamente dimenticato di selezionare la casella di controllo "To-Many Relationship".

Quindi, se ci si imbatte in questo e non si utilizzano i set ordinati, ricontrollare il modello.


1

Ho trovato una correzione per questo bug che funziona per me. Sostituisco solo questo:

[item addSubitemsObject:subItem];

con questo:

item.subitemsObject = subItem;

1

Versione migliore della risposta corretta in SWIFT

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

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

0

Ho trovato che usando il metodo di LeeIII ha funzionato, ma sulla profilazione ha scoperto che era drasticamente lento. Ci sono voluti 15 secondi per analizzare 1000 oggetti. Commentando il codice per aggiungere la relazione trasformato 15 secondi in 2 secondi.

La mia soluzione (che è più veloce, ma molto più brutto) comporta la creazione di una matrice mutevole temporanea poi copiare nel set ordinato quando tutto il parsing è fatto. (questa è una vittoria della prestazione solo se hai intenzione di aggiungere molte relazioni).

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

Spero che questo aiuti qualcun altro!


0

Roberto,

Sono d'accordo che la tua risposta funzionerà per questo, ma tieni presente che esiste già un metodo creato automaticamente per aggiungere un intero set di valori a una relazione. La documentazione di Apple ( come si vede qui nella sezione "Relazioni con molti" o qui nella sezione "Metodi di accesso alle relazioni con molti") li implementa in questo modo:

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

Puoi facilmente compilare il tuo set di relazioni al di fuori dei dati di base e quindi aggiungerle tutte in una volta utilizzando questo metodo. Potrebbe essere meno brutto del metodo che hai suggerito;)


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.