Cara menyalin objek di Objective-C

112

Saya perlu menyalin objek khusus yang memiliki objeknya sendiri. Saya telah membaca dan agak bingung tentang cara mewarisi NSCopying dan cara menggunakan NSCopyObject.

ben
sumber
1
TUTORIAL hebat untuk memahami salinan, dapat diubahSalin dan salinanWithZone
horkavlna

Jawaban:

192

Seperti biasa dengan jenis referensi, ada dua pengertian tentang "salinan". Saya yakin Anda tahu mereka, tapi untuk kelengkapan.

  1. Salinan bitwise. Dalam hal ini, kita hanya menyalin sedikit memori - inilah yang dilakukan NSCopyObject. Hampir selalu, itu bukan yang Anda inginkan. Objek memiliki status internal, objek lain, dll, dan sering kali membuat asumsi bahwa hanya mereka yang menyimpan referensi ke data tersebut. Salinan bitwise mematahkan asumsi ini.
  2. Salinan yang dalam dan logis. Dalam hal ini, kami membuat salinan objek, tetapi tanpa benar-benar melakukannya sedikit demi sedikit - kami menginginkan objek yang berperilaku sama untuk semua maksud dan tujuan, tetapi bukan (harus) tiruan identik memori dari aslinya - manual Objective C menyebut obyek seperti itu "secara fungsional tidak tergantung" dari aslinya. Karena mekanisme untuk membuat salinan "cerdas" ini bervariasi dari satu kelas ke kelas lain, kami meminta objek itu sendiri untuk melakukannya. Ini adalah protokol NSCopying.

Anda menginginkan yang terakhir. Jika ini adalah salah satu objek Anda, Anda hanya perlu mengadopsi protokol NSCopying dan menerapkan - (id) copyWithZone: (NSZone *) zona. Anda bebas melakukan apa pun yang Anda inginkan; meskipun idenya adalah Anda membuat salinan asli dari diri Anda dan mengembalikannya. Anda memanggil copyWithZone di semua bidang Anda, untuk membuat salinan dalam. Contoh sederhananya adalah

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  // We'll ignore the zone for now
  YourClass *another = [[YourClass alloc] init];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Adam Wright
sumber
Tetapi Anda membuat penerima objek yang disalin bertanggung jawab untuk membebaskannya! Tidakkah seharusnya Anda autoreleasemelakukannya, atau apakah saya melewatkan sesuatu di sini?
bobobobo
30
@bobobo: Tidak, aturan dasar manajemen memori Objective-C adalah: Anda mengambil kepemilikan atas sebuah objek jika Anda membuatnya menggunakan metode yang namanya dimulai dengan "alokasi" atau "baru" atau berisi "salinan". copyWithZone:memenuhi kriteria ini, oleh karena itu harus mengembalikan objek dengan jumlah retensi +1.
Steve Madsen
1
@Adam Apakah ada alasan untuk menggunakan allocdaripada allocWithZone:sejak zona itu dilewati?
Richard
3
Nah, zona secara efektif tidak digunakan dalam runtime berbasis OS X modern (yaitu saya pikir mereka benar-benar tidak pernah digunakan). Tapi ya, Anda bisa menelepon allocWithZone.
Adam Wright
25

Dokumentasi Apple mengatakan

Versi subkelas dari metode copyWithZone: harus mengirim pesan ke super terlebih dahulu, untuk memasukkan implementasinya, kecuali jika subkelas diturunkan langsung dari NSObject.

untuk menambah jawaban yang ada

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  YourClass *another = [super copyWithZone:zone];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Saqib Saud
sumber
2
Karena KelasAnda turun langsung dari NSObject, saya rasa tidak perlu di sini
Mike
2
Poin bagus, tapi itu aturan umum, jika itu adalah hierarki kelas yang panjang.
Saqib Saud
8
Saya mendapat error: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Saya kira ini hanya diperlukan ketika kita mewarisi dari beberapa kelas khusus lain yang mengimplementasikancopyWithZone
Sam
1
another.obj = [[obj copyWithZone: zone] autorelease]; untuk semua subclass NSObject. Dan untuk tipe data primitif Anda cukup menetapkannya -> another.someBOOL = self.someBOOL;
hariszaman
@Sam "NSObject sendiri tidak mendukung protokol NSCopying. Subclass harus mendukung protokol dan mengimplementasikan metode copyWithZone:. Versi subclass dari metode copyWithZone: harus mengirim pesan ke super first, untuk menggabungkan implementasinya, kecuali subclass turun secara langsung dari NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

Saya tidak tahu perbedaan antara kode itu dan milik saya, tetapi saya memiliki masalah dengan solusi itu, jadi saya membaca lebih banyak dan menemukan bahwa kita harus mengatur objek sebelum mengembalikannya. Maksud saya sesuatu seperti:

#import <Foundation/Foundation.h>

@interface YourObject : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *line;
@property (strong, nonatomic) NSMutableString *tags;
@property (strong, nonatomic) NSString *htmlSource;
@property (strong, nonatomic) NSMutableString *obj;

-(id) copyWithZone: (NSZone *) zone;

@end


@implementation YourObject


-(id) copyWithZone: (NSZone *) zone
{
    YourObject *copy = [[YourObject allocWithZone: zone] init];

    [copy setNombre: self.name];
    [copy setLinea: self.line];
    [copy setTags: self.tags];
    [copy setHtmlSource: self.htmlSource];

    return copy;
}

Saya menambahkan jawaban ini karena saya memiliki banyak masalah dengan masalah ini dan saya tidak tahu mengapa itu terjadi. Saya tidak tahu perbedaannya, tetapi ini berhasil untuk saya dan mungkin juga berguna untuk orang lain :)

Felipe Quirós
sumber
3
another.obj = [obj copyWithZone: zone];

Saya pikir, baris ini menyebabkan kebocoran memori, karena Anda mengakses objmelalui properti yang (saya asumsikan) dinyatakan sebagai retain. Jadi, jumlah retensi akan ditingkatkan berdasarkan properti dan copyWithZone.

Saya percaya itu harus:

another.obj = [[obj copyWithZone: zone] autorelease];

atau:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
sumber
Tidak, metode alokasi, salin, mutableCopy, baru harus mengembalikan objek yang tidak dirilis secara otomatis.
kovpas
@kovpas, apakah Anda yakin, bahwa Anda mengerti saya kan? Saya tidak berbicara tentang objek yang dikembalikan, saya berbicara tentang bidang datanya.
Szuwar_Jr
ya, saya buruk, maaf. bisakah Anda mengedit jawaban Anda agar saya dapat menghapus minus? :))
kovpas
0

Ada juga penggunaan operator -> untuk menyalin. Sebagai contoh:

-(id)copyWithZone:(NSZone*)zone
{
    MYClass* copy = [MYClass new];
    copy->_property1 = self->_property1;
    ...
    copy->_propertyN = self->_propertyN;
    return copy;
}

Alasan di sini adalah objek yang disalin harus mencerminkan keadaan objek aslinya. The "." operator dapat memperkenalkan efek samping karena yang satu ini memanggil getter yang pada gilirannya mungkin berisi logika.

Alex Nolasco
sumber