Excepción lanzada en los accesos generados por NSOrderedSet

364

En mi aplicación Lion, tengo este modelo de datos:

ingrese la descripción de la imagen aquí

La relación subitemsdentroItem está ordenada .

Xcode 4.1 (compilación 4B110) ha creado para mí el archivo Item.h, Item.m,SubItem.h y SubItem.h.

Aquí está el contenido (autogenerado) 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

Y aquí está el contenido (autogenerado) de Item.m:

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

@implementation Item

@dynamic name;
@dynamic subitems;

@end

Como puede ver, la clase Itemofrece un método llamado addSubitemsObject:. Desafortunadamente, cuando trato de usarlo de esta manera:

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

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

[item addSubitemsObject:subItem];

aparece este error:

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

¿Me puedes ayudar?

Actualizar:

Después de solo 1.787 días desde mi informe de errores, hoy (1 de agosto de 2016) Apple me escribió esto: "Verifique este problema con la última versión beta de iOS 10 y actualice su informe de errores en bugreport.apple.com con sus resultados". . Esperemos que este sea el momento adecuado :)

Dev
fuente
55
Estoy viendo el mismo problema. Ojalá se arregle pronto. Aunque usar el conjunto ordenado mutable directamente es una solución fácil por el momento. Nota: Estoy usando mogenerator, pero supongo que está usando el mismo generador de Apple internamente para esta parte del código generado.
Chad Podoski
12
¡Son casi 2 años! ¿Lo arreglarás en iOS 7, Apple? —— Solo quiero compartir con aquellos que se preguntan si este error sigue ahí: "Sí, lo está".
an0
1
Hace casi dos años, esto sigue siendo un problema en todas las vistas previas de desarrolladores de xcode 5.
Korvin Szanto
2
¿Sigue viendo el problema si usa el accesorio KVC apropiado? (es decir mutableOrderedSetValueForKey:)
quellish
3
Parece que sigue siendo un problema en Mavericks.
Tim

Respuestas:

263

Reproduje su configuración tanto con su modelo de datos como con el mío con diferentes nombres. Obtuve el mismo error en ambos casos.

Parece un error en el código autogenerado de Apple.

TechZen
fuente
6060
El ID de error es 10114310. Se informó el 13 de septiembre de 2011 pero hoy (15 de enero de 2012) todavía está "abierto". Es increíble, considerando la cantidad de personas que tienen el mismo problema.
Dev
14
Actualización: hoy (11 de mayo de 2012) el error # 10114310 todavía está abierto, 241 días después de mi informe (13 de septiembre de 2011). Increíble.
Dev
23
Acabo de mencionar esto con un ingeniero de Apple durante una de las sesiones de CoreData Lab en WWDC. Reconocen el problema y que es un error genuino, y por lo que he visto tiene el estado "crítico", pero, por supuesto, no hay promesa de cuándo lo solucionarán. No creo que se arregle en iOS6 / Mountain Lion. Creo que sería bueno duplicar aún más este radar. Actualmente tiene alrededor de 25 dup's, ¡cuantos más, mejor!
DaGaMs
40
¡Acabo de revisar hoy, todavía está allí en iOS 7 GM / OMG! No puedo creerlo ...
an0
79
Actualización: 797 días, han pasado 2 nuevas versiones principales de iOS e innumerables versiones de Xcode desde que llené el error # 10114310. Y todavía está "abierto". Increíble.
Dev
244

Estoy de acuerdo en que puede haber un error aquí. Modifiqué la implementación del set setter de objetos para que se agregue correctamente a un NSMutableOrderedSet.

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

La reasignación del conjunto a self.subitems garantizará que se envíen las notificaciones Will / DidChangeValue.

InitJason
fuente
Su fragmento de código era justo lo que necesitaba para solucionar este problema. Espero que Apple solucione el problema eventualmente, pero hasta ahora no he visto ningún problema con el uso de su enfoque.
Christopher Hujanen
Recibo este error cuando intento implementar esta solución alternativa. [__NSArrayI isEqualToSet:]: selector no reconocido enviado a la instancia ... Esto generalmente es de un elemento que se ha lanzado, pero no puedo encontrar dónde, alguien ejecuta ¿dentro de esto?
DerekH
@DerekH isEqualToSet es un método que solo NSSet tiene, por lo que supongo que ha convertido, creado o está tratando un puntero como un NSArray antes de pasar de nuevo al NSManagedObject, que debería, si por alguna razón, llamar a isEqualToOrderedSet para determinar si el conjunto necesita incluso cambiar o quedarse como está.
InitJason
3
@MarkAmery Probado. Verificado El setter dinámico self.subitems envía las notificaciones. Entonces la solución JLust es correcta.
bernstein
3
Esta es una buena respuesta, pero es ineficiente. Copia todo el conjunto ordenado, modifica y luego vuelve a copiarlo. El efecto no es solo un golpe para el conjunto ordenado, sino que se envían notificaciones de que cada vez que se modifica el conjunto ordenado, ¡se cambia todo su contenido! Si este conjunto ordenado se usa para una UITable, por ejemplo, esto podría tener serias ramificaciones para la actualización. He esbozado en mi solución exactamente de dónde viene el error y he mostrado un método más eficiente para evitar el error.
Owen Godfrey
111

Decidí mejorar la solución implementando todos los métodos requeridos:

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
fuente
1
¿Cuál es el tipo de choque? 'removeObjectFromSubitemsAtIndex' no elimina estos subelementos, todavía existen en su almacenamiento, es solo la forma de eliminar la relación entre los objetos.
Dmitry Makarenko
2
kItemsKey es una constante que se agregó solo por conveniencia en las llamadas a métodos KVO. Es un nombre de relación ordenada para la que está escribiendo sus métodos.
Dmitry Makarenko
1
Eso es lo que creo que es. Gracias. Pero mi problema es que los datos no se guardan en la base de datos mediante estos métodos.
Bagusflyer
44
!!!!!!!!! Simplemente copiando el código y cambiando los nombres de los métodos, ¡funciona perfectamente! Esta es la respuesta más rápida.
flypig
1
Esto es excelente, pero no es necesario crear la copia temporal del conjunto ordenado. El culpable es willChangeValueForKey:withSetMutation:usingObjects, que has evitado con éxito. Después de eso, solo use [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]o [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]según corresponda. Vea mi respuesta para más detalles.
Owen Godfrey
38

Sí, este es definitivamente un error de Core Data. Escribí una solución basada en ObjC-Runtime hace un tiempo, pero en ese momento pensé que se solucionaría pronto. De todos modos, no tuve tanta suerte, así que lo publiqué en GitHub como KCOrderedAccessorFix . Evite el problema en todas sus entidades:

[managedObjectModel kc_generateOrderedSetAccessors];

Una entidad en particular:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

O solo para una relación:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];
Sterling Archer
fuente
Me pregunto si esto entrará en conflicto con la solución real de Apple o no.
tia
3
Esto no debería entrar en conflicto con la solución de Apple, ya que su objetivo es anular la implementación de Apple sin importar qué. Cuando / si Apple lo soluciona realmente, tal vez agregue una - (BOOL)kc_needsOrderedSetAccessorFix;o algo que verifique la versión Foundation / iOS.
Sterling Archer el
2
Ya hay un KCOrderedAccessorFix.podspec en el repositorio principal de CocoaPods. Entonces, para vincular esto a sus proyectos, simplemente puede agregar "pod 'KCOrderedAccessorFix'" a su Podfile
Anton Matosov
Esto tuvo algunos problemas con iOS 8 (firmas de método incorrectas para objc_msg_send)
NSTJ
En iOS9 funciona, ¡buen trabajo! Esta es la mejor solución, ¡no es necesario cambiar nada en su código!
Borzh
32

En lugar de hacer una copia, sugiero usar el descriptor de acceso en NSObject para obtener acceso al NSMutableOrderedSet de las relaciones.

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

por ejemplo, las Notas de lanzamiento de Core Data para iOS v5.0 se refieren a esto.

En una breve prueba funcionó en mi aplicación.

Stephan
fuente
1
No se pueden refactorizar cadenas literales tan fácilmente. El compilador puede escribir check self.subitems si usa código.
logancautrell
1
@logancautrell sí, esto es correcto. Depende de la prioridad del caso de uso específico. En general, me concentro en ahorrar recursos, especialmente en este caso porque esto era solo una solución.
Stephan
2
Sin NSStringFromSelector(@selector(subitems))embargo, la cadena literal puede ser reemplazada por :)
Ja͢ck
17

He rastreado el error. Ocurre enwillChangeValueForKey:withSetMutation:usingObjects: .

Esta llamada desencadena una cadena de notificaciones que puede ser difícil de rastrear y, por supuesto, los cambios en un respondedor pueden tener implicaciones para otro, lo que sospecho es por qué Apple no ha hecho nada.

Sin embargo, está bien en Set y solo las operaciones de Set en un OrderedSet funcionan mal. Eso significa que solo hay cuatro métodos que deben modificarse. Por lo tanto, todo lo que hice fue convertir las operaciones Set a sus operaciones Array equivalentes. Estos funcionan perfectamente y los gastos generales mínimos (pero necesarios).

En un nivel crítico, esta solución sufre de un defecto crítico; si está agregando objetos y uno de los objetos ya existe, entonces no se agrega ni se mueve al final de la lista ordenada (no sé cuál). En cualquier caso, el índice ordenado esperado del objeto en el momento en que llegamos didChangees diferente de lo previsto. Esto puede romper las aplicaciones de algunas personas, pero no afecta las mías, ya que solo agrego nuevos objetos o confirmo sus ubicaciones finales antes de agregarlos.

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

Por supuesto, hay una solución más fácil. es como sigue;

- (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
fuente
Lástima que todos los demás parecen haber pasado por alto esta respuesta, definitivamente parece ser la mejor solución.
George
Esta solución tiene un rendimiento mucho mejor que las que usan ordenadoSetWithOrderedSet para hacer un conjunto local. Eso tiene una gran sobrecarga cuando tienes grandes conjuntos de datos. La solución más fácil parece ser solo una versión refactorizada de la inicial con los métodos que no se muestran.
David Pettigrew
1
Todavía veo un bloqueo en addChildren: *** Finalización de la aplicación debido a la excepción no detectada 'NSInvalidArgumentException', razón: '- [TrackHistory insertTrackpoints: atIndexes:]: selector no reconocido enviado a la instancia 0x1702b1b20'
Victor Bogdan
@OwenGodfrey Para la solución más fácil, ¿dónde está implementando estos métodos? Recibo una excepción: [Parent insertObject: inChildrenAtIndex:] selector no reconocido enviado a la instancia 0x6180000ac480.
Dalmazio
su variable es "Parent" con una "P" mayúscula? ¿Eso significa que se le llama a la clase "Parent" o tiene una variable de instancia llamada "Parent"? Si mi clase es Parent, entonces implementé estos métodos en la parte inferior de Parent, pero necesitaría llamarlo en una instancia, que probablemente se llamaría "parent" con una "p" en minúscula, ya que estos no son métodos de clase .
Owen Godfrey
10

Los documentos de Apple para muchos Relaciones dice: usted debe tener acceso al conjunto mutable proxy o conjunto ordenado usando

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

La modificación de este conjunto agregará o eliminará relaciones a su objeto administrado. Accediendo al conjunto ordenado mutable usando el descriptor de acceso ya sea con [] o. la notación está mal y fallará.

Nicolas Manzini
fuente
3
Para ser justos, los documentos también dicen: "o uno de los métodos de mutadores de relación generados automáticamente (ver Métodos de acceso generados dinámicamente):"
Matt
Vale, vale ... tienes razón. Bueno, digamos que esa es la forma más simple de trabajar ...
Nicolas Manzini
9

Recibí el mismo error, la solución @LeeIII funcionó para mí (¡gracias!). Sugiero modificarlo ligeramente:

  • use la categoría del objetivo-c para almacenar el nuevo método (para que no perdamos nuestro método si se vuelve a generar el artículo)
  • comprobar si ya tenemos conjunto mutable

Contenido 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
fuente
Buen punto para mover este código a la categoría. Pero aún así tenemos que aceptar agregar / eliminar real con will / setPrimitiveValue / didChange llamadas como en la respuesta @Dmitry Makarenko.
Vladimir Shutyuk
8

Si está utilizando mogenerator, entonces en lugar de

[parentObject add<Child>sObject:childObject];

simplemente use:

[[parent object <child>sSet] addObject:childObject];
Καrτhικ
fuente
Debido a que mogenerator se encarga del código adicional, de lo contrario tendría que escribir y simplemente permite el acceso al objeto de conjunto subyacente.
Augαrτhικ
Parece que se ha solucionado un problema que significa que mogenerator generará cuerpos corregidos ... github.com/dmakarenko/mogenerator/commit/…
combinatoria el
1
Estoy usando mogeneratorpero todavía tengo el error.
Colas
7

Personalmente, acabo de reemplazar las llamadas a los métodos generados por CoreData con llamadas directas al método como se describe en otra solución de @Stephan:

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

Esto elimina la necesidad de categorías que luego puedan entrar en conflicto con una solución de Apple para el código generado cuando se corrige el error.

¡Esto tiene la ventaja adicional de ser la forma oficial de hacerlo!

Grouchal
fuente
Esto da el siguiente error: '[<CLASE 0x20886d10> valueForUndefinedKey:]: esta clase no es compatible con la codificación del valor de clave para los subelementos de clave'.
jmstone617
Si bien todavía me molesta que esto no figure en los problemas conocidos de Apple (he abierto un radar por el gesto aparentemente inútil que es), esta solución me ha funcionado perfectamente.
Scott Corscadden
Ojalá hubiera visto esta respuesta antes; Estaba usando la respuesta más votada hasta que recientemente investigué un poco y finalmente implementé exactamente lo que tienes aquí :)
Ja͢ck
¿Por qué se addObject:llama dos veces?
Jason Moore
5

Parece que si vincula al padre con el hijo estableciendo el padre al hijo y no al revés, funciona sin fallar.

Entonces si lo haces:

[child setParent:parent]

en vez de

[parent setChildObects:child]

Debería funcionar, al menos funciona en iOS 7 y no tuvo ningún problema con la relación.

Cata
fuente
1
No hace mucho bien cuando ambas partes son demasiadas. Entonces no hay una relación clara entre padres e hijos.
fatuhoku
3

He tenido el mismo problema, pero solo cuando intenté algo diferente a lo que había estado haciendo. No puedo ver el código para subItem, pero supondré que tiene un enlace inverso al elemento. Llamemos a este enlace de reverencia, "parentItem", entonces la solución más fácil es esta:

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;

El efecto es que hace uso del código propio de Apple y es simple y limpio. Además, el conjunto se agrega automáticamente y todos los observadores se actualizan. No hay problema.

Owen Godfrey
fuente
Esto esta muy bien. Resuelve todo el problema y también lo mantiene ordenado. Todavía loco que el error todavía está presente. Otro beneficio de esta respuesta es que si regenera sus modelos de datos básicos no tiene que volver a escribir sus correcciones de errores. ¡Gracias!
Johan S
Mira mi otra respuesta. Seguí el error con más detalle. Esta sigue siendo la forma más fácil, pero el otro método es el mejor porque abre más posibilidades.
Owen Godfrey el
¡Guauu! ¡¡¡Finalmente!!! ¡Gracias! (Intentó su otro código, pero recibió errores, se envió algo sobre ese tipo incorrecto en [self didChange: NSKeyValueChangeInsertion valuesAtIndexes: indexSet forKey: ChildrenKey];)
Leonard Pauli
3

Simplemente me equivoqué con este problema y lo resolví usando una implementación mucho más simple que las otras descritas aquí. Simplemente uso los métodos disponibles enNSManagedObject para tratar las relaciones cuando no uso subclases.

Un ejemplo de implementación para insertar una entidad en una NSOrderedSetrelación se vería así:

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

Esto funciona perfectamente y es lo que estaba usando antes de pasar a las NSManagedObjectsubclases.

Mic Pringle
fuente
3

Este problema se me ocurrió al migrar un proyecto de Objective-C a Swift 2 con XCode 7 . Ese proyecto solía funcionar, y por una buena razón: estaba usando MOGenerator que tenía métodos de reemplazo para solucionar este error. Pero no todos los métodos requieren un reemplazo.

Así que aquí está la solución completa con una clase de ejemplo, confiando lo más posible en los accesos predeterminados.

Digamos que tenemos una lista con artículos ordenados

Primero, una victoria rápida si tiene una relación uno a muchos, lo más fácil es simplemente hacer:

item.list = list

en vez de

list.addItemsObject(item)

Ahora, si esa no es una opción , esto es lo que puede hacer:

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

Por supuesto, si está utilizando Objective-C, puede hacer exactamente lo mismo ya que aquí es donde obtuve la idea en primer lugar :)

Nycen
fuente
3

Estoy de acuerdo en que tal vez haya un error aquí. Modifiqué la implementación del add object> setter para que se agregue correctamente a un NSMutableOrderedSet.

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

Al reasignar el conjunto a self.subitems se asegurará de que se envíen las notificaciones Will / DidChangeValue>.

Leelll, ¿está seguro de que después de dicha configuración personalizada de valores NSMutableOrderedSet almacenados en ese conjunto CoreData guardará correctamente en la base de datos? No lo comprobé, pero parece que CoreData no sabe nada acerca de NSOrderedSet y espera que NSSet sea un contenedor de muchas relaciones.

DisableR
fuente
Para que CoreData devuelva o tome un objeto NSOrderedSet, se deben cumplir varias condiciones como se muestra en esta pregunta iniciada. Los errores más comunes que veo cuando las personas que comparten mi código han sido un desarrollador que no ejecuta Lion. El marco NSOrderedSets no está disponible en snowleopard. Pero sí, no he visto que esto falle, aunque no estoy seguro de que sea mejor en rendimiento. Supongo que esto toma todo el conjunto y lo reemplaza en lugar de simplemente insertar el registro deseado.
InitJason
2

Creo que a todos les falta el verdadero problema. No está en los métodos de acceso sino en el hecho de que NSOrderedSetno es una subclase de NSSet. Entonces, cuando -interSectsSet:se llama con un conjunto ordenado como argumento, falla.

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

 [setB intersectsSet:setA];

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

Parece que la solución es cambiar la implementación de los operadores de conjunto para que manejen los tipos de forma transparente. No hay razón por la que un-intersectsSet: deba funcionar con un conjunto ordenado o no ordenado.

La excepción ocurre en la notificación de cambio. Presumiblemente en el código que maneja la relación inversa. Ya que solo sucede si establezco una relación inversa.

Lo siguiente hizo el truco para mí

@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
Entropía
fuente
2

Acabo de recibir el problema en Swift (Xcode 6.1.1).

La respuesta fue NO CODIFICAR NINGÚN MÉTODO O COSAS ADICIONALES en sus subclases de NSManagedObject. Creo que es un error del compilador. Muy extraño error ...

Espero eso ayude ..

lobodart
fuente
3
Entonces, si no puedo implementar las otras soluciones, ¿qué debo hacer para solucionar esto?
Ben Leggiero
2

Resolví este problema estableciendo el inverso en No inverso, no sé por qué, tal vez haya Apple Bug.ingrese la descripción de la imagen aquí

LevinYan
fuente
1

Tengo la misma situación con un elemento llamado "señales" en lugar de "subpuntos". La solución con tempset funciona en mis pruebas. Además, tuve un problema con removeSignals: método. Esta anulación parece funcionar:

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

Si hay una mejor manera de hacerlo, avíseme. La entrada de mis valores nunca supera los 10-20 elementos, por lo que el rendimiento no es una gran preocupación; no obstante, señale cualquier cosa relevante.

Gracias,

Damien

Damien Del Russo
fuente
1

Encontré esta pregunta buscando en Google el mensaje de error, y solo quería señalar que me encontré con este error de una manera ligeramente diferente (sin usar conjuntos ordenados). Esta no es una respuesta a la pregunta dada, pero la estoy publicando aquí en caso de que sea útil para cualquier otra persona que se encuentre con esta pregunta mientras busca.

Estaba agregando una nueva versión del modelo, y agregué algunas relaciones a los modelos existentes, y definí los métodos add * Object en el archivo de encabezado yo mismo. Cuando intenté llamarlos, recibí el error anterior.

Después de revisar mis modelos, me di cuenta de que había olvidado estúpidamente marcar la casilla de verificación "Relación con muchos".

Entonces, si te estás encontrando con esto y no estás usando conjuntos ordenados, revisa tu modelo.

BenV
fuente
1

Encontré una solución para este error que funciona para mí. Acabo de reemplazar esto:

[item addSubitemsObject:subItem];

con este:

item.subitemsObject = subItem;
Bimawa
fuente
1

Mejor versión de la respuesta correcta en SWIFT

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

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

Descubrí que usar el método de LeeIII funcionaba, pero en la elaboración de perfiles descubrí que era drásticamente lento. Tardó 15 segundos en analizar 1000 artículos. Comentar el código para agregar la relación convirtió 15 segundos en 2 segundos.

Mi solución (que es más rápida pero mucho más fea) implica crear una matriz mutable temporal y luego copiarla en el conjunto ordenado cuando se realiza todo el análisis. (esto es solo una ganancia de rendimiento si va a agregar muchas relaciones).

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

Espero que esto ayude a alguien más!

Robert
fuente
0

Robert

Estoy de acuerdo en que su respuesta funcionará para esto, pero tenga en cuenta que ya existe un método creado automáticamente para agregar un conjunto completo de valores a una relación. La Documentación de Apple ( como se ve aquí en la sección "Relaciones de muchos a muchos" o aquí en la sección "Métodos personalizados de acceso a muchas relaciones") los implementa de esta manera:

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

Puede compilar fácilmente su conjunto de relaciones fuera de los datos centrales y luego agregarlos todos a la vez utilizando este método. Puede ser menos feo que el método que sugirió;)

JiuJitsuCoder
fuente