Menyalin jauh NSArray

119

Apakah ada fungsi bawaan yang memungkinkan saya untuk menyalin secara mendalam NSMutableArray?

Saya melihat sekeliling, beberapa orang mengatakan [aMutableArray copyWithZone:nil]karya sebagai salinan dalam. Tapi saya mencoba dan tampaknya salinan yang dangkal.

Saat ini saya melakukan penyalinan secara manual dengan forloop:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

tapi saya ingin solusi yang lebih bersih, lebih ringkas.

ivanTheTerrible
sumber
44
Salinan dalam dan dangkal @Genericrich adalah istilah yang didefinisikan dengan cukup baik dalam pengembangan perangkat lunak. Google.com dapat membantu
Andrew Grant
1
mungkin beberapa kebingungan adalah karena perilaku -copypada koleksi yang tidak dapat diubah berubah antara Mac OS X 10.4 dan 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (gulir ke bawah ke "Koleksi yang tidak dapat diubah dan perilaku penyalinan")
pengguna102008
1
@AndrewGrant Pada pemikiran lebih lanjut, dan dengan hormat, saya tidak setuju bahwa deep copy adalah istilah yang didefinisikan dengan baik. Bergantung pada sumber yang Anda baca, tidak jelas apakah rekursi tak terbatas ke dalam struktur data bersarang merupakan persyaratan operasi 'salinan dalam'. Dengan kata lain, Anda akan mendapatkan jawaban yang bertentangan tentang apakah operasi penyalinan yang membuat objek baru yang anggotanya adalah salinan dangkal dari anggota objek asli adalah operasi 'salinan dalam' atau tidak. Lihat stackoverflow.com/a/6183597/1709587 untuk beberapa diskusi tentang ini (dalam konteks Java, tetapi tetap relevan).
Mark Amery
@AndrewGrant Saya harus mencadangkan @MarkAmery dan @Genericrich. Salinan dalam didefinisikan dengan baik jika kelas root yang digunakan dalam koleksi dan semua elemennya dapat disalin. Ini tidak terjadi dengan NSArray (dan koleksi objc lainnya). Jika elemen tidak diimplementasikan copy, apa yang harus dimasukkan ke dalam "salinan dalam"? Jika elemennya adalah koleksi lain, copytidak benar-benar menghasilkan salinan (dari kelas yang sama). Jadi saya pikir sangat valid untuk berdebat tentang jenis salinan yang diinginkan dalam kasus tertentu.
Nikolai Ruhe
@NikolaiRuhe Jika sebuah elemen tidak mengimplementasikan NSCopying/ -copy, maka elemen itu tidak dapat disalin — jadi Anda tidak boleh mencoba membuat salinannya, karena itu bukanlah kemampuan yang dirancang untuk dimilikinya. Dalam hal penerapan Cocoa, objek yang tidak dapat disalin sering kali memiliki beberapa status backend C yang terkait dengannya, jadi meretas salinan langsung objek dapat menyebabkan kondisi balapan atau lebih buruk. Jadi untuk menjawab "apa yang harus dimasukkan ke dalam 'salinan dalam'" - A dipertahankan ref. Satu-satunya benda yang dapat Anda letakkan di mana saja jika Anda memiliki non- NSCopyingobjek.
Slipp D. Thompson

Jawaban:

210

Seperti dokumentasi Apple tentang deep copy secara eksplisit menyatakan:

Jika Anda hanya membutuhkan salinan sedalam satu tingkat :

NSMutableArray *newArray = [[NSMutableArray alloc] 
                             initWithArray:oldArray copyItems:YES];

Kode di atas membuat array baru yang anggotanya merupakan salinan dangkal dari anggota array lama.

Perhatikan bahwa jika Anda perlu menyalin secara mendalam seluruh struktur data bersarang - yang oleh dokumen Apple tertaut disebut sebagai salinan dalam yang benar - maka pendekatan ini tidak akan cukup. Silakan lihat jawaban lain di sini untuk itu.

François P.
sumber
Sepertinya jawaban yang benar. API menyatakan setiap elemen mendapat pesan [elemen copyWithZone:], yang mungkin Anda lihat. Jika Anda sebenarnya melihat bahwa pengiriman [NSMutableArray copyWithZone: nil] tidak menyalin dalam, maka array array mungkin tidak disalin dengan benar menggunakan metode ini.
Ed Marty
7
Saya tidak berpikir ini akan bekerja seperti yang diharapkan. Dari dokumen Apple: "Metode copyWithZone: melakukan penyalinan dangkal . Jika Anda memiliki koleksi kedalaman arbitrer, meneruskan YES untuk parameter bendera akan menjalankan salinan yang tidak dapat diubah dari tingkat pertama di bawah permukaan. Jika Anda lolos NO, mutabilitas tingkat pertama tidak terpengaruh. Dalam kedua kasus, mutabilitas dari semua tingkat yang lebih dalam tidak terpengaruh. "Pertanyaan SO menyangkut salinan yang dapat berubah dalam.
Joe D'Andrea
7
ini BUKAN salinan dalam
Daij-Djan
9
Ini adalah jawaban yang tidak lengkap. Ini menghasilkan salinan dalam satu tingkat. Jika ada tipe yang lebih kompleks dalam Array itu tidak akan memberikan salinan yang dalam.
Cameron Lowell Palmer
7
Ini bisa menjadi salinan yang dalam, tergantung pada bagaimana copyWithZone:diimplementasikan pada kelas penerima.
devios1
62

Satu-satunya cara yang saya tahu untuk melakukan ini dengan mudah adalah dengan mengarsipkan dan kemudian segera membatalkan pengarsipan array Anda. Rasanya seperti sedikit peretasan, tetapi sebenarnya secara eksplisit disarankan dalam Dokumentasi Apple tentang menyalin koleksi , yang menyatakan:

Jika Anda memerlukan salinan dalam yang benar, seperti saat Anda memiliki larik larik, Anda dapat mengarsipkan dan kemudian membatalkan pengarsipan koleksi, asalkan semua konten sesuai dengan protokol NSCoding. Contoh dari teknik ini ditunjukkan pada Daftar 3.

Kode 3 Salinan dalam yang benar

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];

Tangkapannya adalah bahwa objek Anda harus mendukung antarmuka NSCoding, karena ini akan digunakan untuk menyimpan / memuat data.

Versi Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
sumber
12
Menggunakan NSArchiver dan NSUnarchiver adalah solusi yang sangat berat dari segi kinerja, jika array Anda besar. Menulis metode kategori NSArray generik yang menggunakan protokol NSCopying akan melakukan triknya, menyebabkan 'retensi' sederhana dari objek yang tidak dapat diubah dan 'salinan' nyata dari objek yang bisa berubah.
Nikita Zhuk
Sebaiknya berhati-hati tentang biayanya, tetapi apakah NSCoding benar-benar lebih mahal daripada NSCopying yang digunakan dalam initWithArray:copyItems:metode ini? Solusi pengarsipan / pengarsipan ini tampaknya sangat berguna, mengingat berapa banyak kelas kontrol yang sesuai dengan NSCoding tetapi tidak dengan NSCopying.
Wienke
Saya akan sangat menyarankan agar Anda tidak pernah menggunakan pendekatan ini. Serialisasi tidak pernah lebih cepat dari sekedar menyalin memori.
Brett
1
Jika Anda memiliki objek khusus, pastikan untuk menerapkan encodeWithCoder dan initWithCoder agar sesuai dengan protokol NSCoding.
pengguna523234
Jelas kebijaksanaan programmer sangat disarankan saat menggunakan serialisasi.
VH-NZZ
34

Salinan secara default memberikan salinan yang dangkal

Itu karena memanggil copysama dengan yang copyWithZone:NULLjuga dikenal sebagai menyalin dengan zona default. The copypanggilan tidak menghasilkan salinan yang mendalam. Dalam kebanyakan kasus, ini akan memberi Anda salinan yang dangkal, tetapi dalam hal apa pun itu tergantung pada kelasnya. Untuk diskusi menyeluruh, saya merekomendasikan Topik Pemrograman Koleksi di situs Pengembang Apple.

initWithArray: CopyItems: memberikan salinan dalam satu tingkat

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding adalah cara yang disarankan Apple untuk memberikan salinan dalam

Untuk salinan dalam yang benar (Array of Arrays) Anda perlu NSCodingdan mengarsipkan / membatalkan pengarsipan objek:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
sumber
1
Ini benar. Terlepas dari popularitas atau edge case yang dioptimalkan sebelum waktunya tentang memori, kinerja. Selain itu, peretasan ser-derser untuk salinan dalam ini digunakan di banyak lingkungan bahasa lainnya. Kecuali jika ada obj dedupe, ini menjamin salinan dalam yang bagus sepenuhnya terpisah dari aslinya.
1
Berikut adalah pengembang
7

Untuk Dictonary

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

Untuk Array

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);

Darshit Shah
sumber
4

Tidak, tidak ada sesuatu yang dibangun ke dalam kerangka untuk ini. Koleksi kakao mendukung salinan dangkal (dengan metode copyatau arrayWithArray:) tetapi bahkan tidak berbicara tentang konsep salinan mendalam.

Ini karena "salinan dalam" mulai menjadi sulit untuk didefinisikan saat konten koleksi Anda mulai menyertakan objek khusus Anda sendiri. Apakah "salinan dalam" berarti setiap objek dalam grafik objek merupakan referensi unik yang berhubungan dengan setiap objek dalam grafik objek asli?

Jika ada beberapa NSDeepCopyingprotokol hipotetis , Anda dapat mengatur ini dan membuat keputusan di semua objek Anda, tetapi sayangnya tidak ada. Jika Anda mengontrol sebagian besar objek dalam grafik, Anda dapat membuat protokol ini sendiri dan mengimplementasikannya, tetapi Anda perlu menambahkan kategori ke kelas Foundation seperlunya.

Jawaban @ AndrewGrant yang menyarankan penggunaan pengarsipan / pembongkaran kunci adalah cara yang tidak berkinerja baik tetapi benar dan bersih untuk mencapai hal ini untuk objek sewenang-wenang. Buku ini bahkan melangkah lebih jauh sehingga menyarankan untuk menambahkan kategori ke semua objek yang melakukan persis seperti itu untuk mendukung penyalinan mendalam.

Ben Zotto
sumber
2

Saya memiliki solusi jika mencoba mendapatkan salinan dalam untuk data yang kompatibel dengan JSON.

Hanya mengambil NSDatadari NSArraymenggunakan NSJSONSerializationdan kemudian menciptakan JSON Object, ini akan membuat salinan baru dan segar lengkap NSArray/NSDictionarydengan referensi memori baru dari mereka.

Tetapi pastikan objek NSArray / NSDictionary dan anak-anaknya harus dapat bersambung JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
sumber
1
Anda harus menentukan NSJSONReadingMutableContainerskasus penggunaan dalam pertanyaan ini.
Nikolai Ruhe