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 :)

Dev
fonte
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.

TechZen
fonte
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.

InitJason
fonte
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];
    }
}
Dmitry Makarenko
fonte
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];
Sterling Archer
fonte
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.

Stephan
fonte
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];
    }]];
}
Owen Godfrey
fonte
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á.

Nicolas Manzini
fonte
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
Danik
fonte
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];
Καrτhικ
fonte
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!

Grouchal
fonte
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.

Cata
fonte
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.

Owen Godfrey
fonte
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.

Mic Pringle
fonte
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 :)

Nycen
fonte
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.

DisableR
fonte
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
Entropia
fonte
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 ..

lobodart
fonte
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

LevinYan
fonte
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

Damien Del Russo
fonte
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.

BenV
fonte
1

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

[item addSubitemsObject:subItem];

com isso:

item.subitemsObject = subItem;
Bimawa
fonte
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
emreoktem
fonte
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!

Robert
fonte
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;)

JiuJitsuCoder
fonte