Exceção lançada nos acessadores gerados por NSOrderedSet


364

No meu aplicativo Lion, eu tenho este modelo de dados:

insira a descrição da imagem aqui

O relacionamento subitemsinterno Item é ordenado .

Xcode 4.1 (compilação 4B110) criou para mim o arquivo Item.h, Item.m, SubItem.he SubItem.h.

Aqui está o conteúdo (gerado automaticamente) 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

E aqui está o conteúdo (gerado automaticamente) de Item.m:

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

@implementation Item

@dynamic name;
@dynamic subitems;

@end

Como você pode ver, a classe Itemoferece um método chamado addSubitemsObject:. Infelizmente, ao tentar usá-lo desta maneira:

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

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

[item addSubitemsObject:subItem];

este erro aparece:

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

Pode me ajudar?

Atualizar:

Após apenas 1.787 dias do meu relatório de erros, hoje (1 de agosto de 2016) a Apple me escreveu o seguinte: "Verifique este problema com a versão beta mais recente do iOS 10 e atualize seu relatório de erros em bugreport.apple.com com seus resultados." . Espero que este seja o momento certo :)


5
Estou vendo o mesmo problema. Espero que seja corrigido em breve. Embora o uso do conjunto ordenado mutável diretamente seja uma solução fácil por enquanto. Nota: Estou usando o mogenerator, mas presumo que ele esteja usando o mesmo gerador da Apple internamente para esta parte do código gerado.
Chad Podoski 10/10

12
Faz quase 2 anos! Você vai consertá-lo no iOS 7, Apple? —— Eu só quero compartilhar com aqueles que se perguntam se esse bug ainda existe: "Sim, está."
an0

11
Há quase dois anos, esse ainda é um problema em todas as visualizações do desenvolvedor do xcode 5.
precisa saber é o seguinte

2
Você ainda vê o problema se usar o acessador KVC apropriado? (ie mutableOrderedSetValueForKey:)
quellish 05/04

3
Parece ainda haver um problema no Mavericks.
Tim

Respostas:


263

Reproduzi sua configuração com seu modelo de dados e um com meus nomes diferentes. Eu recebi o mesmo erro nos dois casos.

Parece um bug no código gerado automaticamente pela Apple.


60
O ID do bug é 10114310. Foi relatado em 13-set-2011, mas hoje (15-jan-2012) ainda está "aberto". É incrível, considerando o número de pessoas que têm o mesmo problema.
Dev

14
Atualização: hoje (11 de maio de 2012) o bug # 10114310 ainda está aberto 241 dias após o meu relatório (13 de setembro de 2011). Inacreditável.
Dev

23
Acabei de falar sobre isso com um engenheiro da Apple durante uma das sessões do CoreData Lab na WWDC. Eles reconhecem o problema e são um bug genuíno, e pelo que vi, ele tem o status "crítico", mas é claro que não há promessa de quando o consertarão. Eu não acho que isso será corrigido no iOS6 / Mountain Lion. Eu acho que seria bom duplicar ainda mais esse radar. Atualmente, tem cerca de 25 dup's, quanto mais, melhor!
DaGaMs

40
Apenas verificado hoje, ele ainda está lá no iOS 7 GM / OMG! Eu não posso acreditar…
an0

79
Atualização: 797 dias, duas novas versões principais do iOS e inúmeras versões do Xcode se passaram desde que eu preenchi o bug # 10114310. E ainda está "aberto". Inacreditável.
Dev

244

Concordo que pode haver um erro aqui. Modifiquei a implementação do setter set add para acrescentar corretamente a um NSMutableOrderedSet.

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

A reatribuição do conjunto como self.subitems garantirá que as notificações Will / DidChangeValue sejam enviadas.


Seu snippet de código era exatamente o que eu precisava para solucionar esse problema. Espero que a Apple resolva o problema eventualmente, mas até agora não vi nenhum problema ao usar sua abordagem.
Christopher Hujanen 02/12/19

Estou recebendo esse erro ao tentar implementar esta solução alternativa. [__NSArrayI isEqualToSet:]: seletor não reconhecido enviado para a instância ... Isso geralmente é de um item que foi lançado, mas não consegue encontrar onde, alguém executa nisso?
precisa saber é o seguinte

@DerekH isEqualToSet é um método que apenas o NSSet possui, portanto, acho que você converteu, criou ou está tratando um ponteiro como um NSArray antes de retornar ao NSManagedObject, que deveria, por qualquer motivo, estar chamando isEqualToOrderedSet para determinar se o conjunto precisa até mudar ou ficar como está.
InitJason

3
@MarkAmery Tested. Verificado. O configurador dinâmico self.subitems envia as notificações. Portanto, a solução JLust está correta.
Bernstein

3
Esta é uma boa resposta, mas é ineficiente. Você copia todo o conjunto solicitado, modifica e depois copia novamente. O efeito não é apenas um acerto no conjunto ordenado, mas são enviadas notificações de que toda vez que o conjunto ordenado é alterado, todo o seu conteúdo é alterado! Se este conjunto ordenado for usado para uma UITable, por exemplo, isso pode ter implicações sérias na atualização. Descrevi na minha solução exatamente de onde vem o erro e mostrei um método mais eficiente para contornar o erro.
quer tocar hoje

111

Decidi melhorar a solução implementando todos os métodos necessários:

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

11
Qual é o tipo de falha? 'removeObjectFromSubitemsAtIndex' não exclui esses subitens, eles ainda existem no seu armazenamento, é apenas o caminho para remover o relacionamento entre os objetos.
Dmitry Makarenko

2
kItemsKey é uma constante que foi adicionada apenas por conveniência nas chamadas de métodos KVO. É um nome de relacionamento ordenado para o qual você está escrevendo seus métodos.
Dmitry Makarenko # /

11
É o que eu acho que é. Obrigado. Mas meu problema é que os dados não são salvos no banco de dados usando esses métodos.
Bagusflyer

4
!!!!!!!!! Apenas copiando o código e alterando os nomes dos métodos, funciona perfeitamente !!! Esta é a resposta mais rápida.
flypig

11
Isso é fantástico, mas a criação da cópia temporária do conjunto ordenado é desnecessária. O culpado é willChangeValueForKey:withSetMutation:usingObjectsque você evitou com sucesso. Depois disso, basta usar [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]ou [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]conforme apropriado. Veja minha resposta para detalhes.
quer tocar hoje

38

Sim, esse é definitivamente um bug do Core Data. Escrevi uma correção baseada em ObjC-Runtime há um tempo, mas na época achei que seria corrigida em breve. De qualquer forma, não tive essa sorte, então eu publiquei no GitHub como KCOrderedAccessorFix . Solução alternativa do problema em todas as suas entidades:

[managedObjectModel kc_generateOrderedSetAccessors];

Uma entidade em particular:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

Ou apenas para um relacionamento:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];

Gostaria de saber se isso vai entrar em conflito com a correção real da Apple ou não?
tia

3
Isso não deve entrar em conflito com a correção da Apple, pois seu objetivo é substituir a implementação da Apple, não importa o quê. Quando / se isso for realmente corrigido pela Apple, talvez eu adicione - (BOOL)kc_needsOrderedSetAccessorFix;ou algo que verifique a versão do Foundation / iOS.
Sterling Archer

2
Já existe um KCOrderedAccessorFix.podspec no repositório principal do CocoaPods. Portanto, para vincular isso aos seus projetos, você pode simplesmente adicionar "pod 'KCOrderedAccessorFix'" ao seu Podfile
Anton Matosov

Isto teve alguns problemas com o iOS 8 (assinaturas de método incorreto para objc_msg_send)
NSTJ

No iOS9 funciona, bom trabalho! Esta é a melhor solução de todos os tempos, não há necessidade de alterar nada no seu código!
Borzh #

32

Em vez de fazer uma cópia, sugiro usar o acessador no NSObject para obter acesso ao NSMutableOrderedSet dos relacionamentos.

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

por exemplo, as Notas da versão do Core Data para iOS v5.0 se referem a isso.

Em um teste curto, funcionou no meu aplicativo.


11
Não é possível refatorar cadeias literais com a mesma facilidade. O compilador pode digitar check self.subitems se você usar o código.
Loganutrell

11
@logancautrell sim, isso está correto. Depende da prioridade do caso de uso específico. Em geral, concentro-me em economizar recursos, especialmente neste caso, porque essa foi apenas uma solução alternativa.
Stephan

2
A string literal pode ser substituído por NSStringFromSelector(@selector(subitems))embora :)
Jack

17

Eu rastreei o bug. Isso ocorre em willChangeValueForKey:withSetMutation:usingObjects:.

Essa ligação desencadeia uma cadeia de notificações que podem ser difíceis de rastrear e, é claro, alterações em um atendedor podem ter implicações em outro, o que suspeito é o motivo pelo qual a Apple não fez nada.

No entanto, está tudo bem no Set e são apenas as operações do Set em um OrderedSet que funcionam mal. Isso significa que existem apenas quatro métodos que precisam ser alterados. Portanto, tudo o que fiz foi converter as operações Set em suas operações Array equivalentes. Eles funcionam perfeitamente e com sobrecarga mínima (mas necessária).

Em um nível crítico, esta solução sofre de uma falha crítica; se você estiver adicionando objetos e um dos objetos já existir, ele não será adicionado ou movido para o final da lista ordenada (não sei qual). Em ambos os casos, o índice ordenado esperado do objeto no momento em que chegamos didChangeé diferente do que era antecipado. Isso pode quebrar os aplicativos de algumas pessoas, mas não afeta os meus, pois apenas adiciono novos objetos ou confirmo seus locais finais antes de adicioná-los.

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

Claro, existe uma solução mais fácil. é o seguinte;

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

Pena que todo mundo parece ter esquecido essa resposta, definitivamente parece ser a melhor solução.
George

Essa solução tem um desempenho muito melhor do que o uso do ordersSetWithOrderedSet para criar um conjunto local. Isso tem uma grande sobrecarga quando você tem grandes conjuntos de dados. A solução mais fácil parece ser apenas uma versão refatorada da inicial com os métodos não mostrados.
David Pettigrew 5/05

11
Eu ainda estou vendo um acidente em addChildren: *** terminação app devido à exceção não capturada 'NSInvalidArgumentException', razão: '- [insertTrackpoints TrackHistory: atIndexes:]: selector não reconhecido enviada à instância 0x1702b1b20'
Victor Bogdan

@OwenGodfrey Para uma solução mais fácil, onde você está implementando esses métodos? Estou recebendo uma exceção: [Parent insertObject: inChildrenAtIndex:] seletor não reconhecido enviado para a instância 0x6180000ac480.
Dalmazio

sua variável é "Parent" com uma maiúscula "P"? Isso significa que você chama a classe "Pai" ou tem uma variável de instância chamada "Pai"? Se minha classe for Parent, eu implementei esses métodos na parte inferior de Parent, mas você precisaria chamá-lo em uma instância, que provavelmente seria denominada "parent" com uma letra minúscula "p", pois esses não são métodos de classe .
Owen Godfrey

10

Os documentos da Apple Para Muitas Relações dizem: você deve acessar o conjunto mutável de proxy ou o conjunto ordenado usando

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

A modificação deste conjunto adicionará ou removerá relações ao seu objeto gerenciado. Acessando o conjunto ordenado mutável usando o acessador, com [] ou. notação está errada e falhará.


3
Para ser justo, os documentos também dizem: "ou um dos métodos mutadores de relacionamento gerados automaticamente (consulte Métodos de acesso gerados dinamicamente):"
Matt

Ok ok ... você está certo. Bem, então, vamos dizer que essa é a maneira mais simples de trabalho ...
Nicolas Manzini

9

Recebeu o mesmo erro, a solução @LeeIII funcionou para mim (obrigado!). Sugiro modificá-lo ligeiramente:

  • use a categoria de objetivo-c para armazenar o novo método (para que não percam nosso método se Item for gerado novamente)
  • verifique se já temos conjunto mutável

Conteúdo 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

Bom ponto para mover esse código na categoria. Mas ainda precisamos adotar / remover com as chamadas will / setPrimitiveValue / didChange, como na resposta do @Dmitry Makarenko.
Vladimir Shutyuk

8

Se você estiver usando o mogenerator, em vez de

[parentObject add<Child>sObject:childObject];

basta usar:

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

Como o mogenerator cuida do código extra, você precisaria escrever e simplesmente permitir o acesso ao objeto do conjunto subjacente.
Καrτhικ

Parece que uma correção acaba de ser confirmada, o que significa que o mogenerator irá gerar corpos corrigidos ... github.com/dmakarenko/mogenerator/commit/…
combinatorial

11
Estou usando, mogeneratormas ainda tenho o bug.
Colas

7

Pessoalmente, acabei de substituir as chamadas para os métodos gerados pelo CoreData por chamadas diretas ao método, conforme descrito em outra solução por @Stephan:

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

Isso elimina a necessidade de categorias que posteriormente possam entrar em conflito com uma solução da Apple para o código gerado quando o bug for corrigido.

Isso tem a vantagem adicional de ser a maneira oficial de fazê-lo!


Isso fornece o seguinte erro: '[<CLASS 0x20886d10> valueForUndefinedKey:]: essa classe não é compatível com a codificação do valor da chave para os subitens da chave.'
jmstone617

Embora ainda me irrite que isso não esteja listado nos problemas conhecidos da Apple (abri um radar para o gesto aparentemente fútil que é), essa solução funcionou perfeitamente para mim.
22613 Scott Scott Corscadden

Gostaria de ter visto essa resposta anteriormente; Eu estava usando a maior resposta votado até que eu recentemente fiz alguma escavação e, finalmente, implementado exatamente o que você tem aqui :)
Jack

Por que é addObject:chamado duas vezes?
Jason Moore

5

Parece que se você vincular o pai ao filho, definindo o pai para o filho e não o contrário, funcionará sem travar.

Então, se você fizer:

[child setParent:parent]

ao invés de

[parent setChildObects:child]

Deve funcionar, pelo menos funciona no iOS 7 e não teve problemas com o relacionamento.


11
Não faz muito bem quando os dois lados são muitos. Então não há uma relação clara entre pais e filhos.
Fatuhoku

3

Eu tive o mesmo problema, mas apenas quando tentei algo diferente do que estava fazendo. Não consigo ver o código do subItem, mas assumirei que ele possui um link reverso para o item. Vamos chamar esse link de reverência, "parentItem", então a solução mais fácil é a seguinte:

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;

O efeito é que ele usa o próprio código da apple e é simples e limpo. Além disso, o conjunto é adicionado automaticamente e todos os observadores são atualizados. Sem problemas.


Isso é muito legal. Ele resolve todo o problema e o mantém em ordem. Ainda insano que o bug ainda esteja presente. Outro benefício desta resposta é que, se você regenerar seus modelos de dados principais, não precisará reescrever suas correções. Obrigado!
Johan S

Veja minha outra resposta. Eu rastreei o bug com mais detalhes. Essa ainda é a maneira mais fácil, mas o outro método é o melhor, porque abre mais possibilidades.
Owen Godfrey #

Uau! Finalmente!!! Obrigado! (Tentou o seu outro código, mas os erros tem, algo sobre que tipo errado foi enviado em [auto didChange: NSKeyValueChangeInsertion valuesAtIndexes: indexSet forKey: ChildrenKey];)
Leonard Pauli

3

Acabei de entrar em conflito com esse problema e resolvi-o usando uma implementação muito mais simples do que as outras descritas aqui. Simplesmente uso os métodos disponíveis NSManagedObjectpara lidar com relacionamentos quando não estou usando subclasses.

Um exemplo de implementação para inserir uma entidade em um NSOrderedSetrelacionamento ficaria assim:

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

Isso funciona perfeitamente e é o que eu estava usando antes de passar para as NSManagedObjectsubclasses.


3

Este problema ocorreu ao migrar um projeto do Objective-C para o Swift 2 com o XCode 7 . Esse projeto funcionou e por um bom motivo: eu estava usando o MOGenerator que tinha métodos de substituição para corrigir esse bug. Mas nem todos os métodos exigem uma substituição.

Então, aqui está a solução completa com uma classe de exemplo, contando com acessadores padrão o máximo possível.

Digamos que tenhamos uma lista com itens solicitados

Primeiro, uma vitória rápida, se você tiver um relacionamento de um para muitos, o mais fácil é:

item.list = list

ao invés de

list.addItemsObject(item)

Agora, se isso não for uma opção , eis o que você pode fazer:

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

Obviamente, se você estiver usando o Objective-C, poderá fazer exatamente a mesma coisa, pois foi aí que eu tive a ideia em primeiro lugar :)


3

Eu concordo que talvez haja um bug aqui. Modifiquei a implementação do add object> setter para acrescentar corretamente a um NSMutableOrderedSet.

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

A reatribuição do conjunto como self.subitems garantirá que as notificações Will / DidChangeValue> sejam enviadas.

Leelll, você tem certeza de que, após a configuração personalizada dos valores NSMutableOrderedSet armazenados nesse conjunto, serão salvos no banco de dados corretamente pelo CoreData? Não verifiquei isso, mas parece que o CoreData não sabe nada sobre NSOrderedSet e espera que o NSSet seja um contêiner de relacionamento com muitos.


Para que CoreData retorne ou pegue um objeto NSOrderedSet, várias condições devem ser atendidas, como esta pergunta iniciada mostrou. Os erros mais comuns que vejo quando as pessoas que compartilham meu código são desenvolvedores que não executam o Lion. A estrutura NSOrderedSets não está disponível no snowleopard. Mas sim, eu não vi isso falhar, apesar de não ter certeza de que isso é melhor no desempenho. Eu acho que isso pega o conjunto inteiro e o substitui, em vez de apenas inserir o registro desejado.
InitJason

2

Acho que todo mundo está perdendo o problema real. Não está nos métodos do acessador, mas no fato de NSOrderedSetnão ser uma subclasse de NSSet. Portanto, quando -interSectsSet:é chamado com um conjunto ordenado como argumento, ele falha.

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

 [setB intersectsSet:setA];

falha com *** -[NSSet intersectsSet:]: set argument is not an NSSet

Parece que a correção é alterar a implementação dos operadores de conjunto para que eles manipulem os tipos de maneira transparente. Não há razão para que a -intersectsSet:deva funcionar com um conjunto ordenado ou não ordenado.

A exceção acontece na notificação de alteração. Presumivelmente no código que lida com o relacionamento inverso. Como isso só acontece se eu estabelecer um relacionamento inverso.

O seguinte fez o truque para mim

@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

Acabei de receber o problema no Swift (Xcode 6.1.1).

A resposta foi NÃO CODIFICAR QUALQUER MÉTODO OU COISA ADICIONAL nas subclasses do NSManagedObject. Eu acho que é um erro do compilador. Bug muito estranho ..

Espero que ajude ..


3
Portanto, se não consigo implementar as outras correções, o que devo fazer para corrigir isso?
Ben Leggiero

2

Resolvi esse problema definindo o inverso como No Inverse, não sei por que, talvez exista o Apple Bug.insira a descrição da imagem aqui


1

Eu tenho a mesma situação com um item chamado "sinais" em vez de "subitens". A solução com tempset funciona nos meus testes. Além disso, tive um problema com o método removeSignals:. Essa substituição parece funcionar:

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

Se houver uma maneira melhor de fazer isso, entre em contato. Minha entrada de valores nunca é superior a 10 a 20 itens, portanto o desempenho não é uma grande preocupação - no entanto, aponte algo relevante.

Obrigado,

Damien


1

Encontrei essa pergunta pesquisando a mensagem de erro no Google e só queria salientar que encontrei esse erro de uma maneira ligeiramente diferente (sem usar conjuntos ordenados). Essa não é uma resposta para a pergunta em questão, mas estou postando aqui para o caso de ser útil para qualquer pessoa que se deparar com essa pergunta enquanto estiver pesquisando.

Eu estava adicionando uma nova versão do modelo, adicionei alguns relacionamentos aos modelos existentes e defini os métodos add * Object no arquivo de cabeçalho. Quando tentei ligar para eles, recebi o erro acima.

Depois de revisar meus modelos, percebi que havia me esquecido estupidamente de marcar a caixa de seleção "Relacionamento com muitos".

Portanto, se você estiver enfrentando isso e não estiver usando conjuntos ordenados, verifique seu modelo.


1

Encontrei uma correção para esse bug que funciona para mim. Eu apenas substituo isso:

[item addSubitemsObject:subItem];

com isso:

item.subitemsObject = subItem;

1

Melhor versão da resposta correta no SWIFT

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

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

0

Descobri que o método de LeeIII funcionava, mas na criação de perfis, era drasticamente lento. Demorou 15 segundos para analisar 1000 itens. Comentar o código para adicionar o relacionamento transformou 15 segundos em 2 segundos.

Minha solução alternativa (que é mais rápida, mas muito mais feia) envolve a criação de uma matriz mutável temporária e a cópia no conjunto ordenado quando toda a análise é concluída. (isso é apenas uma vitória no desempenho se você deseja adicionar muitos relacionamentos).

@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 isso ajude outra pessoa!


0

Robert,

Concordo que sua resposta funcionará para isso, mas lembre-se de que já existe um método criado automaticamente para adicionar todo um conjunto de valores a um relacionamento. A documentação da Apple ( como pode ser vista aqui na seção "Relacionamentos para muitos" ou aqui na seção "Métodos personalizados de acessor de relacionamento para muitos") os implementa da seguinte maneira:

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

Você pode compilar facilmente seu conjunto de relacionamentos fora dos dados principais e adicioná-los todos de uma vez usando esse método. Pode ser menos feio do que o método que você sugeriu;)


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.