Pengecualian dilemparkan ke dalam NSOrderedSet yang dihasilkan pengakses

364

Di aplikasi Lion saya, saya memiliki model data ini:

masukkan deskripsi gambar di sini

Hubungan subitemsdi dalam Item diperintahkan .

Xcode 4.1 (build 4B110) telah menciptakan bagi saya file Item.h, Item.m, SubItem.hdan SubItem.h.

Berikut adalah konten (di-autogenerasi) dari 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

Dan di sini adalah konten (di-autogenerasi) dari Item.m:

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

@implementation Item

@dynamic name;
@dynamic subitems;

@end

Seperti yang Anda lihat, kelas Itemmenawarkan metode yang disebut addSubitemsObject:. Sayangnya, ketika mencoba menggunakannya dengan cara ini:

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

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

[item addSubitemsObject:subItem];

kesalahan ini muncul:

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

Bisakah kamu membantuku?

Memperbarui:

Setelah hanya 1.787 hari sejak laporan bug saya, hari ini (1 Agustus 2016) Apple menulis ini kepada saya: "Harap verifikasi masalah ini dengan versi beta iOS 10 terbaru dan perbarui laporan bug Anda di bugreport.apple.com dengan hasil Anda." . Mari berharap ini adalah waktu yang tepat :)

Dev
sumber
5
Saya melihat masalah yang sama. Semoga bisa segera diperbaiki. Meskipun menggunakan set memerintahkan bisa berubah secara langsung adalah solusi yang mudah untuk saat ini. Catatan: Saya menggunakan mogenerator, tetapi saya menganggapnya menggunakan generator Apple yang sama secara internal untuk bagian kode yang dihasilkan ini.
Chad Podoski
12
Sudah hampir 2 tahun! Apakah Anda akan memperbaikinya di iOS 7, Apple? —— Saya hanya ingin berbagi dengan mereka yang bertanya-tanya apakah bug ini masih ada: "Ya, benar."
AN0
1
Sangat dekat dengan dua tahun sekarang, ini masih menjadi masalah di semua pratinjau pengembang xcode 5.
Korvin Szanto
2
Apakah Anda masih melihat masalah jika Anda menggunakan accessor KVC yang sesuai? (mis. mutableOrderedSetValueForKey:)
quellish
3
Tampaknya masih menjadi masalah di Mavericks.
Tim

Jawaban:

263

Saya mereproduksi pengaturan Anda baik dengan model data Anda dan saya sendiri dengan nama yang berbeda. Saya mendapat kesalahan yang sama dalam kedua kasus.

Sepertinya ada bug dalam kode autogenerated Apple.

TechZen
sumber
60
ID bug adalah 10114310. Dilaporkan pada 13-Sep-2011 tetapi hari ini (15-Jan-2012) masih "terbuka". Sungguh luar biasa, mengingat jumlah orang yang memiliki masalah yang sama.
Dev
14
Pembaruan: hari ini (11 Mei 2012) bug # 10114310 masih terbuka, 241 hari setelah laporan saya (13 September 2011). Luar biasa.
Dev
23
Saya baru saja membicarakan ini dengan seorang insinyur Apple selama salah satu sesi Lab CoreData di WWDC. Mereka mengakui masalah ini dan itu adalah bug asli, dan dari apa yang saya lihat itu memiliki status "kritis", tetapi tentu saja tidak ada janji kapan mereka akan memperbaikinya. Saya tidak berpikir itu akan diperbaiki di iOS6 / Mountain Lion. Saya pikir akan lebih baik untuk menggandakan radar ini lebih jauh. Saat ini memiliki sekitar 25 dup, semakin banyak semakin baik!
DaGaMs
40
Baru diperiksa hari ini, masih ada di iOS 7 GM / OMG! Saya tidak percaya ...
dan
79
Pembaruan: 797 hari, 2 versi iOS utama baru, dan versi Xcode yang tak terhitung jumlahnya telah berlalu sejak saya mengisi bug # 10114310. Dan itu masih "terbuka." Luar biasa.
Dev
244

Saya setuju bahwa mungkin ada bug di sini. Saya telah memodifikasi implementasi add objek setter untuk ditambahkan dengan benar ke NSMutableOrderedSet.

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

Menugaskan ulang set ke self.subitems akan memastikan bahwa notifikasi Will / DidChangeValue dikirim.

InitJason
sumber
Cuplikan kode Anda tepat seperti yang saya perlukan untuk mengatasi masalah ini. Harapan Apple akhirnya memperbaiki masalah, tapi sejauh ini saya belum melihat masalah dengan menggunakan pendekatan Anda.
Christopher Hujanen
Saya mendapatkan kesalahan ini ketika mencoba untuk mengimplementasikan penyelesaian ini .. [__NSArrayI isEqualToSet:]: pemilih yang tidak dikenal dikirim ke instance ... Ini biasanya dari item yang telah dirilis, tetapi tidak dapat menemukan di mana, siapa pun menjalankan dalam hal ini?
DerekH
@DerekH isEqualToSet adalah metode yang hanya dimiliki NSSet, jadi tebakan saya adalah Anda telah mengonversi, membuat, atau memperlakukan pointer sebagai NSArray sebelum meneruskan kembali ke NSManagedObject, yang seharusnya jika ada alasan memanggil isEqualToOrderedSet untuk menentukan apakah kebutuhan yang disetel diperlukan. untuk bahkan mengubah atau dibiarkan apa adanya.
InitJason
3
@MarkAmery Diuji. Diverifikasi. Setter dinamis self.subitems mengirimkan pemberitahuan. Jadi solusi JLust benar.
bernstein
3
Ini adalah jawaban yang bagus, tetapi tidak efisien. Anda menyalin seluruh set yang dipesan, memodifikasi dan kemudian menyalinnya kembali. Efeknya bukan hanya hit ke set yang dipesan, tetapi pemberitahuan dikirim bahwa setiap kali set yang dipesan diubah, seluruh isinya berubah! Jika set yang dipesan ini digunakan untuk UITable misalnya, ini dapat memiliki konsekuensi serius untuk memperbarui. Saya telah menguraikan solusi saya di mana kesalahan itu berasal dan saya telah menunjukkan metode yang lebih efisien untuk mem-bypass kesalahan.
Owen Godfrey
111

Saya telah memutuskan untuk meningkatkan solusi dengan menerapkan semua metode yang diperlukan:

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
sumber
1
Apa jenis kecelakaannya? 'removeObjectFromSubitemsAtIndex' tidak menghapus subtitle ini, mereka masih ada di penyimpanan Anda, itu hanya cara untuk menghapus hubungan antara objek.
Dmitry Makarenko
2
kItemsKey adalah konstanta yang ditambahkan hanya untuk kenyamanan dalam panggilan metode KVO. Ini adalah nama hubungan yang dipesan tempat Anda menulis metode.
Dmitry Makarenko
1
Itulah yang saya pikirkan. Terima kasih. Tetapi masalah saya adalah bahwa data tidak disimpan ke database dengan menggunakan metode ini.
Bagusflyer
4
!!!!!!!!! Hanya menyalin kode dan mengubah nama metode, ini berfungsi dengan sempurna !!! Ini jawaban tercepat.
flypig
1
Ini hebat, tetapi membuat salinan sementara dari himpunan yang diperintahkan tidak perlu. Pelakunya adalah willChangeValueForKey:withSetMutation:usingObjects, yang berhasil Anda hindari. Setelah itu, gunakan saja [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values]atau [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values]sesuai kebutuhan. Lihat jawaban saya untuk detailnya.
Owen Godfrey
38

Ya, ini jelas merupakan bug Data Inti. Saya menulis perbaikan berbasis ObjC-Runtime beberapa waktu lalu, tetapi pada saat itu saya pikir itu akan segera diperbaiki. Bagaimanapun, tidak ada keberuntungan, jadi saya mempostingnya di GitHub sebagai KCOrderedAccessorFix . Atasi masalah pada semua entitas Anda:

[managedObjectModel kc_generateOrderedSetAccessors];

Satu entitas khususnya:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

Atau hanya untuk satu hubungan:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];
Sterling Archer
sumber
Saya ingin tahu apakah ini akan bertentangan dengan perbaikan nyata dari Apple atau tidak?
tia
3
Ini seharusnya tidak bertentangan dengan perbaikan Apple, karena tujuannya adalah untuk mengesampingkan implementasi Apple apa pun yang terjadi. Ketika / jika ini benar-benar diperbaiki oleh Apple, mungkin saya akan menambahkan - (BOOL)kc_needsOrderedSetAccessorFix;atau sesuatu yang memeriksa versi Foundation / iOS.
Sterling Archer
2
Sudah ada KCOrderedAccessorFix.podspec di master repo CocoaPods. Jadi, untuk menautkan ini ke proyek Anda, Anda cukup menambahkan "pod 'KCOrderedAccessorFix'" ke Podfile Anda
Anton Matosov
Ini memiliki beberapa masalah dengan iOS 8 (tanda tangan metode yang salah untuk objc_msg_send)
NSTJ
Di iOS9 berfungsi, kerja bagus! Ini adalah solusi terbaik yang pernah ada, tidak perlu mengubah apa pun dalam kode Anda!
Borzh
32

Alih-alih membuat salinan, saya sarankan untuk menggunakan accessor di NSObject untuk mendapatkan akses ke NSMutableOrderedSet dari hubungan tersebut.

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

mis. Catatan Rilis Data Inti untuk iOS v5.0 lihat ini.

Dalam tes singkat itu berfungsi di aplikasi saya.

Stephan
sumber
1
Tidak dapat dengan mudah memperbaiki string literal. Kompiler dapat mengetik cek self.subitems jika Anda menggunakan kode.
logancautrell
1
@logancautrell ya ini benar. Itu tergantung pada prioritas kasus penggunaan khusus. Secara umum saya fokus pada penghematan sumber daya terutama dalam hal ini karena ini hanya solusi.
Stephan
2
String literal dapat digantikan oleh NSStringFromSelector(@selector(subitems)):)
Ja͢ck
17

Saya telah melacak bug. Itu terjadi pada willChangeValueForKey:withSetMutation:usingObjects:.

Panggilan ini memicu serangkaian pemberitahuan yang mungkin sulit dilacak, dan tentu saja perubahan pada satu responden mungkin berdampak pada yang lain, yang saya curigai adalah alasan Apple tidak melakukan apa pun.

Namun, tidak apa-apa di Set dan satu-satunya operasi Set pada OrderedSet yang tidak berfungsi. Itu berarti hanya ada empat metode yang perlu diubah. Oleh karena itu, yang saya lakukan hanyalah mengonversi operasi Tetapkan ke operasi Array yang setara. Ini berfungsi dengan baik dan biaya overhead minimal (tapi perlu).

Pada tingkat kritis, solusi ini memang menderita satu kelemahan kritis; jika Anda menambahkan objek dan salah satu objek sudah ada, maka itu tidak ditambahkan atau dipindahkan ke belakang daftar yang dipesan (saya tidak tahu yang mana). Dalam kedua kasus tersebut, indeks pesanan yang diharapkan dari objek pada saat kami tiba didChangeberbeda dari apa yang diantisipasi. Ini mungkin merusak beberapa aplikasi orang, tetapi itu tidak mempengaruhi saya, karena saya hanya pernah menambahkan objek baru atau saya mengkonfirmasi lokasi terakhir mereka sebelum saya menambahkannya.

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

Tentu saja, ada solusi yang lebih mudah. itu adalah sebagai berikut;

- (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
sumber
Sayang sekali semua orang tampaknya telah mengabaikan jawaban ini, jelas merupakan solusi terbaik.
George
Solusi ini memiliki kinerja yang jauh lebih baik daripada yang menggunakan orderedSetWithOrderedSet untuk membuat set lokal. Itu memiliki overhead yang besar ketika Anda memiliki set data yang besar. Solusi yang lebih mudah tampaknya hanya versi refactored dari yang pertama dengan metode yang tidak ditampilkan.
David Pettigrew
1
Saya masih melihat kerusakan di addChildren: *** Mengakhiri aplikasi karena pengecualian tanpa pengecualian 'NSInvalidArgumentException', alasan: '- [TrackHistory insertTrackpoints: atIndexes:]: pemilih yang tidak dikenal dikirim ke instance 0x1702b1b20'
Victor Bogdan
@OwenGodfrey Untuk solusi yang lebih mudah, di mana Anda menerapkan metode ini? Saya mendapatkan pengecualian: [Induk insertObject: inChildrenAtIndex:] pemilih tidak dikenal dikirim ke instance 0x6180000ac480.
Dalmazio
variabel Anda adalah "Induk" dengan huruf kapital "P"? Apakah itu berarti Anda memanggil kelas "Parent", atau apakah Anda memiliki variabel instan bernama "Parent"? Jika kelas saya adalah Induk, maka saya menerapkan metode ini di bagian bawah Induk, tetapi Anda perlu menyebutnya sebagai contoh, yang lebih mungkin disebut "induk" dengan huruf kecil "p", karena ini bukan metode kelas .
Owen Godfrey
10

Dokumen Apple To Many Relations mengatakan: Anda harus mengakses set proxy yang dapat berubah atau set memerintahkan menggunakan

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

Mengubah set ini akan menambah atau menghapus relasi ke objek terkelola Anda. Mengakses perangkat yang dapat diatur yang dapat diubah menggunakan accessor baik dengan [] atau. notasi salah dan akan gagal.

Nicolas Manzini
sumber
3
Agar adil, dokumen juga mengatakan: "atau salah satu metode hubungan mutator yang dibuat secara otomatis (lihat Metode Accessor yang Dihasilkan Secara Dinamis):"
Matt
Oke oke ... kamu benar. Kalau begitu, katakanlah itu cara kerja yang paling sederhana ...
Nicolas Manzini
9

Menerima kesalahan yang sama, solusi @LeeIII bekerja untuk saya (terima kasih!). Saya sarankan sedikit memodifikasinya:

  • gunakan kategori objektif-c untuk menyimpan metode baru (jadi kami tidak akan kehilangan metode kami jika Barang dihasilkan lagi)
  • periksa apakah kita sudah memiliki set yang bisa berubah

Isi dari 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
sumber
Poin bagus untuk memindahkan kode ini ke dalam kategori. Tapi kita masih perlu merangkul add / remove aktual dengan panggilan will / setPrimitiveValue / didChange seperti di @Dmitry Makarenko.
Vladimir Shutyuk
8

Jika Anda menggunakan mogenerator, maka alih-alih

[parentObject add<Child>sObject:childObject];

cukup gunakan:

[[parent object <child>sSet] addObject:childObject];
Καrτhικ
sumber
Karena mogenerator menangani kode tambahan yang seharusnya Anda harus tulis dan hanya memungkinkan akses ke objek set yang mendasarinya.
Καrτhικ
Sepertinya perbaikan baru saja dilakukan yang berarti mogenerator akan menghasilkan benda yang diperbaiki ... github.com/dmakarenko/mogenerator/commit/…
kombinatorial
1
Saya menggunakan mogeneratortetapi saya masih memiliki bug.
Colas
7

Secara pribadi saya baru saja mengganti panggilan ke metode yang dihasilkan CoreData dengan panggilan langsung ke metode yang diuraikan dalam solusi lain oleh @Stephan:

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

Ini menghilangkan kebutuhan untuk kategori yang nantinya mungkin bertentangan dengan solusi dari Apple ke kode yang dihasilkan ketika bug diperbaiki.

Ini memiliki nilai tambah sebagai cara resmi untuk melakukannya!

Grouchal
sumber
Ini memberikan kesalahan berikut: '[<CLASS 0x20886d10> valueForUndefinedKey:]: kelas ini tidak sesuai dengan pengkodean nilai kunci untuk subtitle kunci.'
jmstone617
Meskipun saya masih merasa terganggu karena ini tidak terdaftar dalam masalah Apple yang diketahui (saya telah membuka radar untuk isyarat yang tampaknya sia-sia), solusi ini telah bekerja dengan sempurna untuk saya.
Scott Corscadden
Seandainya saya telah melihat jawaban ini sebelumnya; Saya menggunakan jawaban tertinggi sampai saya baru-baru ini melakukan penggalian dan akhirnya menerapkan apa yang Anda miliki di sini :)
Ja --ck
Mengapa addObject:dipanggil dua kali?
Jason Moore
5

Tampaknya jika Anda menautkan orang tua dengan anak dengan mengatur orang tua ke anak dan bukan sebaliknya bekerja tanpa menabrak.

Jadi, jika Anda melakukannya:

[child setParent:parent]

dari pada

[parent setChildObects:child]

Itu harus bekerja, setidaknya itu berfungsi di iOS 7 dan tidak punya masalah dengan hubungan.

Cata
sumber
1
Tidak banyak gunanya ketika kedua belah pihak terlalu banyak. Maka tidak ada hubungan orangtua-anak yang jelas.
fatuhoku
3

Saya memiliki masalah yang sama, tetapi hanya ketika saya mencoba sesuatu yang berbeda dengan apa yang telah saya lakukan. Saya tidak dapat melihat kode untuk subItem, tetapi saya akan menganggap bahwa ia memiliki tautan balik ke item. Mari kita sebut tautan penghormatan ini, "parentItem", maka solusi termudah adalah ini:

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;

Efeknya adalah ia menggunakan kode apel sendiri dan sederhana serta bersih. Selain itu, set secara otomatis ditambahkan ke, dan semua pengamat diperbarui. Tidak masalah.

Owen Godfrey
sumber
Ini sangat bagus. Ini memecahkan seluruh masalah dan membuatnya tetap tertata juga. Masih gila bahwa bug itu masih ada. Manfaat lain dari jawaban ini adalah bahwa jika Anda membuat ulang model data inti Anda, Anda tidak perlu menulis ulang perbaikan bug Anda. Terima kasih!
Johan S
Lihat jawaban saya yang lain. Saya melacak bug lebih detail. Ini masih cara termudah, tetapi metode lain adalah yang terbaik karena membuka lebih banyak kemungkinan.
Owen Godfrey
Wow! Akhirnya!!! Terima kasih! (Mencoba kode Anda yang lain, tetapi mendapat kesalahan, sesuatu tentang tipe yang salah itu dikirim di [self didChange: NSKeyValueChangeInsertion valuesAtIndexes: indexSet forKey: ChildrenKey];)
Leonard Pauli
3

Saya baru saja tersinggung masalah ini, dan mengatasinya menggunakan implementasi yang jauh lebih sederhana daripada yang lain yang dijelaskan di sini. Saya hanya menggunakan metode yang tersedia NSManagedObjectuntuk berurusan dengan hubungan ketika tidak menggunakan subclass.

Contoh implementasi untuk memasukkan entitas ke dalam NSOrderedSethubungan akan terlihat seperti ini:

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

Ini berfungsi dengan baik, dan itulah yang saya gunakan sebelum saya pindah ke NSManagedObjectsubclass.

Mic Pringle
sumber
3

Masalah ini terjadi pada saya saat memigrasi proyek dari Objective-C ke Swift 2 dengan XCode 7 . Proyek itu dulu berfungsi, dan untuk alasan yang bagus: Saya menggunakan MOGenerator yang memiliki metode pengganti untuk memperbaiki bug ini. Tetapi tidak semua metode membutuhkan penggantian.

Jadi, inilah solusi lengkap dengan kelas contoh, sebanyak mungkin menggunakan pengakses default.

Katakanlah kita memiliki Daftar dengan Barang yang dipesan

Pertama kemenangan cepat jika Anda memiliki hubungan satu / ke-banyak, yang paling mudah adalah dengan hanya melakukan:

item.list = list

dari pada

list.addItemsObject(item)

Sekarang, jika itu bukan pilihan , inilah yang dapat Anda lakukan:

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

Tentu saja, jika Anda menggunakan Objective-C, Anda dapat melakukan hal yang persis sama karena di sinilah saya mendapatkan ide itu :)

Nycen
sumber
3

Saya setuju bahwa mungkin ada bug di sini. Saya telah memodifikasi implementasi add object> setter untuk menambahkan dengan benar ke NSMutableOrderedSet.

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

Menugaskan ulang set ke self.subitems akan memastikan bahwa notifikasi Will / DidChangeValue> dikirim.

Leelll, apakah Anda yakin bahwa setelah pengaturan kustom nilai NSMutableOrderedSet yang disimpan dalam set itu akan disimpan ke database dengan benar oleh CoreData? Saya tidak memeriksanya, tetapi sepertinya CoreData tidak tahu apa-apa tentang NSOrderedSet dan mengharapkan NSSet sebagai wadah hubungan yang banyak.

DisableR
sumber
Agar CoreData mengembalikan atau mengambil objek NSOrderedSet, beberapa kondisi harus dipenuhi karena pertanyaan awal ini telah diperlihatkan. Kesalahan paling umum yang saya lihat ketika orang yang membagikan kode saya adalah pengembang yang tidak menjalankan Lion. Kerangka kerja NSOrderedSets tidak tersedia di snowleopard. Tapi ya, saya belum melihat ini gagal, meskipun saya tidak yakin ini yang terbaik pada kinerja. Saya kira ini mengambil seluruh set dan menggantinya, bukan hanya memasukkan catatan yang diinginkan.
InitJason
2

Saya pikir semua orang kehilangan masalah sebenarnya. Ini bukan dalam metode accessor tetapi pada kenyataan bahwa NSOrderedSetitu bukan subkelas dari NSSet. Jadi ketika -interSectsSet:dipanggil dengan set yang diatur sebagai argumen gagal.

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

 [setB intersectsSet:setA];

gagal dengan *** -[NSSet intersectsSet:]: set argument is not an NSSet

Sepertinya perbaikannya adalah untuk mengubah implementasi dari operator yang diatur sehingga mereka menangani jenis secara transparan. Tidak ada alasan mengapa -intersectsSet:harus bekerja dengan set yang dipesan atau tidak teratur.

Pengecualian terjadi dalam pemberitahuan perubahan. Agaknya dalam kode yang menangani hubungan terbalik. Karena itu hanya terjadi jika saya menetapkan hubungan terbalik.

Berikut ini adalah trik untuk saya

@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
Entropi
sumber
2

Saya baru saja mendapat masalah di Swift (Xcode 6.1.1).

Jawabannya adalah JANGAN KODE METODE APA PUN ATAU HAL-HAL TAMBAHAN di subclass NSManagedObject Anda. Saya pikir ini adalah kesalahan kompilator. Bug yang sangat aneh ..

Semoga ini bisa membantu ..

lobodart
sumber
3
Jadi, jika saya tidak bisa menerapkan perbaikan lain, apa yang harus saya lakukan untuk memperbaikinya?
Ben Leggiero
2

Saya memecahkan masalah ini dengan mengatur kebalikannya ke No Inverse, saya tidak tahu mengapa, Mungkin ada Bug Apple.masukkan deskripsi gambar di sini

LevinYan
sumber
1

Saya memiliki situasi yang sama dengan item yang disebut "sinyal" alih-alih "subtitle". Solusi dengan tempset berfungsi dalam pengujian saya. Lebih jauh, saya punya masalah dengan metode removeSignals:. Override ini tampaknya berfungsi:

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

Jika ada cara yang lebih baik untuk melakukan ini, beri tahu saya. Input nilai saya tidak pernah lebih dari 10 -20 item sehingga kinerja tidak terlalu menjadi perhatian - namun tolong tunjukkan hal yang relevan.

Terima kasih,

Damien

Damien Del Russo
sumber
1

Saya menemukan pertanyaan ini dengan googling untuk pesan kesalahan, dan hanya ingin menunjukkan bahwa saya mengalami kesalahan ini dengan cara yang sedikit berbeda (tidak menggunakan set yang dipesan). Ini bukan jawaban yang cukup untuk pertanyaan yang diberikan, tapi saya mempostingnya di sini kalau-kalau itu berguna bagi siapa pun yang menemukan pertanyaan ini saat mencari.

Saya menambahkan versi model baru, dan menambahkan beberapa hubungan ke model yang ada, dan mendefinisikan metode add * Object di file header sendiri. Ketika saya mencoba menelepon mereka, saya mendapatkan kesalahan di atas.

Setelah meninjau model-model saya, saya menyadari bahwa saya dengan bodohnya lupa untuk mencentang kotak "To-Many Relationship".

Jadi, jika Anda mengalami ini dan Anda tidak menggunakan set yang dipesan, periksa model Anda.

BenV
sumber
1

Saya menemukan perbaikan untuk bug ini yang berfungsi untuk saya. Saya hanya mengganti ini:

[item addSubitemsObject:subItem];

dengan ini:

item.subitemsObject = subItem;
Bimawa
sumber
1

Versi yang lebih baik dari jawaban yang benar di SWIFT

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

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

Saya menemukan menggunakan metode oleh LeeIII bekerja, tetapi pada profil menemukan itu sangat lambat. Butuh 15 detik untuk mem-parsing 1000 item. Mengomentari kode untuk menambahkan hubungan berubah 15 detik menjadi 2 detik.

Solusi saya (yang lebih cepat tetapi jauh lebih jelek) melibatkan pembuatan array yang bisa berubah-ubah kemudian menyalin ke set yang diperintahkan ketika semua parsing dilakukan. (Ini hanya kemenangan kinerja jika Anda akan menambahkan banyak hubungan).

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

Saya harap ini membantu orang lain!

Robert
sumber
0

Robert,

Saya setuju jawaban Anda akan berhasil untuk ini, tetapi perlu diingat bahwa sudah ada metode yang dibuat secara otomatis untuk menambahkan seluruh rangkaian nilai ke suatu hubungan. Dokumentasi Apple ( seperti yang terlihat di sini di bagian "Ke-banyak Hubungan" atau di sini di bawah bagian "Metode Aksesor Hubungan Ke Banyak Orang") menerapkannya seperti ini:

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

Anda bisa dengan mudah mengkompilasi set hubungan Anda di luar data inti dan kemudian menambahkan semuanya sekaligus menggunakan metode ini. Mungkin kurang jelek dari metode yang Anda sarankan;)

JiuJitsuCoder
sumber