Properti NSString: salin atau simpan?


331

Katakanlah saya memiliki kelas yang dipanggil SomeClassdengan stringnama properti:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Saya mengerti bahwa nama dapat diberikan NSMutableStringdalam hal ini dapat menyebabkan perilaku yang salah.

  • Untuk string secara umum, apakah selalu lebih baik menggunakan copyatribut bukan retain?
  • Apakah properti "yang disalin" dengan cara apa pun kurang efisien daripada properti "yang dipertahankan-ed" seperti itu?

6
Pertanyaan tindak lanjut: Harus namedirilis deallocatau tidak?
Chetan

7
@ Chetan Ya itu seharusnya!
Jon

Jawaban:


440

Untuk atribut yang tipenya adalah kelas nilai tidak berubah yang sesuai dengan NSCopyingprotokol, Anda hampir selalu harus menentukan copydalam @propertydeklarasi Anda . Menentukan retainadalah sesuatu yang hampir tidak pernah Anda inginkan dalam situasi seperti itu.

Inilah mengapa Anda ingin melakukan itu:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

Nilai Person.nameproperti saat ini akan berbeda tergantung pada apakah properti itu dinyatakan retainatau copy- itu akan menjadi @"Debajit"jika properti ditandai retain, tetapi @"Chris"jika properti ditandai copy.

Karena di hampir semua kasus Anda ingin mencegah mutasi atribut objek di belakangnya, Anda harus menandai properti yang mewakili mereka copy. (Dan jika Anda menulis sendiri penyetel alih-alih menggunakan, @synthesizeAnda harus ingat untuk benar-benar menggunakannya copydaripada retaindi dalamnya.)


61
Jawaban ini mungkin menyebabkan beberapa kebingungan (lihat robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Anda sepenuhnya benar tentang NSString, tetapi saya yakin Anda telah membuat poin ini terlalu umum. Alasan NSString harus disalin adalah karena ia memiliki subclass yang bisa berubah-ubah (NSMutableString). Untuk kelas yang tidak memiliki subkelas yang dapat diubah (khususnya kelas yang Anda tulis sendiri), biasanya lebih baik menyimpannya daripada menyalin agar tidak membuang-buang waktu dan memori.
Rob Napier

63
Alasan Anda salah. Anda tidak boleh menentukan apakah akan menyalin atau mempertahankan berdasarkan waktu / memori, Anda harus menentukan itu berdasarkan semantik yang diinginkan. Itu sebabnya saya secara khusus menggunakan istilah "kelas nilai yang tidak berubah" dalam jawaban saya. Ini juga bukan masalah apakah suatu kelas memiliki subclass yang bisa berubah, atau bisa berubah sendiri.
Chris Hanson

10
Sayang sekali bahwa Obj-C tidak bisa memaksakan imutabilitas berdasarkan tipe. Ini sama dengan kurangnya C ++ dari const transitive. Secara pribadi saya bekerja seolah-olah string selalu abadi. Jika saya perlu menggunakan string yang bisa berubah, saya tidak akan pernah membagikan referensi yang tidak dapat diubah jika saya dapat mengubahnya nanti. Saya menganggap sesuatu yang berbeda sebagai bau kode. Akibatnya - dalam kode saya (yang saya kerjakan sendiri) saya gunakan mempertahankan semua string saya. Jika saya bekerja sebagai bagian dari tim, saya mungkin akan melihat berbagai hal secara berbeda.
philsquared

5
@ Philip Nash: Saya menganggapnya sebagai bau kode untuk menggunakan gaya yang berbeda untuk proyek yang Anda kerjakan sendiri dan proyek yang Anda bagikan dengan orang lain. Di setiap Bahasa / Kerangka ada aturan umum atau gaya yang disepakati pengembang. Mengabaikan mereka dalam proyek-proyek swasta tampaknya salah. Dan untuk alasan Anda, "Dalam kode saya, saya tidak mengembalikan string yang dapat berubah": Itu mungkin bekerja untuk string Anda sendiri, tetapi Anda tidak pernah tahu tentang string yang Anda terima dari kerangka kerja.
Nikolai Ruhe

7
@Nikolai Saya hanya tidak menggunakan NSMutableString, kecuali sebagai tipe "string builder" sementara (dari mana saya langsung mengambil salinan abadi). Saya lebih suka mereka menjadi tipe yang bijaksana - tetapi saya akan membiarkan fakta bahwa salinan bebas untuk melakukan retain jika string asli tidak bisa berubah mengurangi sebagian besar kekhawatiran saya.
philsquared

120

Salinan harus digunakan untuk NSString. Jika itu Mutable, maka itu akan disalin. Jika tidak, maka hanya akan dipertahankan. Tepatnya semantik yang Anda inginkan dalam suatu aplikasi (biarkan tipe melakukan yang terbaik).


1
Saya masih lebih suka bentuk yang bisa berubah dan tidak berubah untuk diam-diam, tetapi saya tidak menyadari sebelumnya bahwa salinan mungkin dipertahankan jika string asli tidak berubah - yang sebagian besar jalan di sana. Terima kasih.
philsquared

25
+1 untuk menyebutkan NSStringproperti yang dinyatakan copyakan mendapatkan retainpula (jika itu tidak berubah, tentu saja). Contoh lain yang bisa saya pikirkan adalah NSNumber.
matm

Apa perbedaan antara jawaban ini dan jawaban turun oleh @ GBY?
Gary Lyn

67

Untuk string secara umum, apakah selalu lebih baik menggunakan atribut copy daripada mempertahankan?

Ya - secara umum selalu menggunakan atribut salin.

Ini karena properti NSString Anda dapat melewati instance NSString atau instance NSMutableString , dan oleh karena itu kami tidak dapat benar-benar menentukan apakah nilai yang diteruskan adalah objek yang tidak dapat diubah atau dapat diubah.

Apakah properti "yang disalin" dengan cara apa pun kurang efisien daripada properti "yang dipertahankan-ed" seperti itu?

  • Jika properti Anda diberikan instance NSString , jawabannya adalah " Tidak " - penyalinan tidak kurang efisien daripada mempertahankan.
    (Ini tidak kalah efisien karena NSString cukup pintar untuk tidak benar-benar melakukan salinan.)

  • Jika properti Anda lulus instance NSMutableString maka jawabannya adalah " Ya " - menyalin kurang efisien daripada mempertahankan.
    (Ini kurang efisien karena alokasi memori dan penyalinan yang sebenarnya harus terjadi, tetapi ini mungkin hal yang diinginkan.)

  • Secara umum, properti "yang disalin" memiliki potensi untuk menjadi kurang efisien - namun melalui penggunaan NSCopyingprotokol, dimungkinkan untuk mengimplementasikan kelas yang "sama efisien" untuk disalin dan dipertahankan. Contoh NSString adalah contoh dari ini.

Secara umum (bukan hanya untuk NSString), kapan saya harus menggunakan "copy" daripada "retain"?

Anda harus selalu menggunakan copyketika Anda tidak ingin keadaan internal properti berubah tanpa peringatan. Bahkan untuk objek yang tidak dapat diubah - objek yang tidak dapat diubah yang ditulis dengan benar akan menangani penyalinan secara efisien (lihat bagian selanjutnya tentang kekekalan dan NSCopying).

Mungkin ada alasan kinerja untuk retainobjek, tetapi itu datang dengan overhead pemeliharaan - Anda harus mengelola kemungkinan keadaan internal berubah di luar kode Anda. Seperti yang mereka katakan - optimalkan yang terakhir.

Tapi, saya menulis kelas saya menjadi tidak berubah - tidak bisakah saya "mempertahankan" saja?

Tidak digunakan copy. Jika kelas Anda benar-benar tidak dapat diubah maka praktik terbaik untuk menerapkan NSCopyingprotokol untuk membuat kelas Anda kembali sendiri ketika copydigunakan. Jika kamu melakukan ini:

  • Pengguna kelas Anda yang lain akan mendapatkan manfaat kinerja saat mereka menggunakannya copy.
  • The copypenjelasan membuat kode Anda sendiri lebih dipertahankan - yang copypenjelasan menunjukkan bahwa Anda benar-benar tidak perlu khawatir tentang objek ini mengubah negara tempat lain.

39

Saya mencoba mengikuti aturan sederhana ini:

  • Apakah saya ingin mempertahankan nilai objek pada titik waktu ketika saya menugaskannya ke properti saya? Gunakan salinan .

  • Apakah saya ingin berpegang pada objek dan saya tidak peduli apa nilai internal saat ini atau di masa depan? Gunakan yang kuat (pertahankan).

Sebagai ilustrasi: Apakah saya ingin berpegang pada nama "Lisa Miller" ( copy ) atau saya ingin berpegang pada orang yang bernama Lisa Miller ( kuat )? Namanya mungkin kemudian berubah menjadi "Lisa Smith", tetapi dia akan tetap menjadi orang yang sama.


14

Melalui contoh ini salin dan simpan dapat dijelaskan seperti:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

jika properti itu tipe copy maka,

salinan baru akan dibuat untuk [Person name]string yang akan menampung konten someNamestring. Sekarang setiap operasi pada someNamestring tidak akan berpengaruh [Person name].

[Person name]dan someNamestring akan memiliki alamat memori yang berbeda.

Tetapi dalam hal mempertahankan,

keduanya [Person name]akan memiliki alamat memori yang sama dengan string somename, hanya jumlah penahan string somename yang akan bertambah 1.

Jadi setiap perubahan dalam string nama wanita akan tercermin dalam [Person name]string.


3

Tentunya menempatkan 'copy' pada deklarasi properti terbang di muka menggunakan lingkungan berorientasi objek di mana objek pada heap dilewatkan dengan referensi - salah satu manfaat yang Anda dapatkan di sini adalah bahwa, ketika mengubah suatu objek, semua referensi ke objek itu lihat perubahan terbaru. Banyak bahasa yang menyediakan kata kunci 'ref' atau sejenis untuk memungkinkan tipe nilai (yaitu struktur di stack) mendapat manfaat dari perilaku yang sama. Secara pribadi, saya akan menggunakan salinan hemat, dan jika saya merasa bahwa nilai properti harus dilindungi dari perubahan yang dilakukan pada objek tempat ia ditugaskan, saya bisa memanggil metode salin objek itu selama penugasan, misalnya:

p.name = [someName copy];

Tentu saja, ketika mendesain objek yang berisi properti itu, hanya Anda yang akan tahu apakah desain mendapat manfaat dari pola tempat penugasan mengambil salinan - Cocoawithlove.com memiliki yang berikut ini untuk mengatakan:

"Anda harus menggunakan copy accessor ketika parameter setter mungkin bisa berubah tetapi Anda tidak bisa mengubah status internal suatu properti tanpa peringatan " - jadi penilaian apakah Anda dapat mempertahankan nilai untuk berubah secara tak terduga adalah milik Anda sendiri. Bayangkan skenario ini:

//person object has details of an individual you're assigning to a contact list.

Contact *contact = [[[Contact alloc] init] autorelease];
contact.name = person.name;

//person changes name
[[person name] setString:@"new name"];
//now both person.name and contact.name are in sync.

Dalam hal ini, tanpa menggunakan salinan, objek kontak kami mengambil nilai baru secara otomatis; jika kami menggunakannya, kami harus memastikan secara manual bahwa perubahan terdeteksi dan disinkronkan. Dalam hal ini, mempertahankan semantik mungkin diinginkan; di tempat lain, salinan mungkin lebih sesuai.


1
@interface TTItem : NSObject    
@property (nonatomic, copy) NSString *name;
@end

{
    TTItem *item = [[TTItem alloc] init];    
    NSString *test1 = [NSString stringWithFormat:@"%d / %@", 1, @"Go go go"];  
    item.name = test1;  
    NSLog(@"-item.name: point = %p, content = %@; test1 = %p", item.name, item.name, test1);  
    test1 = [NSString stringWithFormat:@"%d / %@", 2, @"Back back back"];  
    NSLog(@"+item.name: point = %p, content = %@, test1 = %p", item.name, item.name, test1);
}

Log:  
    -item.name: point = 0x9a805a0, content = 1 / Go go go; test1 = 0x9a805a0  
    +item.name: point = 0x9a805a0, content = 1 / Go go go, test1 = 0x9a84660

0

Anda harus menggunakan copy setiap saat untuk mendeklarasikan properti NSString

@property (nonatomic, copy) NSString* name;

Anda harus membaca ini untuk informasi lebih lanjut tentang apakah ia mengembalikan string yang tidak dapat diubah (jika string yang dapat diubah dilewati) atau mengembalikan string yang dipertahankan (jika string yang tidak dapat diubah telah dilewatkan)

Referensi Protokol NSCopying

Terapkan NSCopying dengan mempertahankan yang asli alih-alih membuat salinan baru ketika kelas dan isinya tidak berubah

Nilai Objek

Jadi, untuk versi abadi kita, kita bisa melakukan ini:

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

-1

Karena nama adalah (tidak dapat diubah) NSString, salin atau simpan tidak ada bedanya jika Anda menetapkan NSStringnama yang lain. Dengan kata lain, salin berperilaku seperti mempertahankan, meningkatkan jumlah referensi satu. Saya pikir itu adalah optimasi otomatis untuk kelas yang tidak dapat diubah, karena mereka tidak dapat diubah dan tidak perlu dikloning. Tetapi ketika a NSMutalbeString mstrdiatur ke nama, konten mstrakan disalin demi kebenaran.


1
Anda mengacaukan tipe yang dideklarasikan dengan tipe yang sebenarnya. Jika Anda menggunakan properti "retain" dan menetapkan NSMutableString, NSMutableString itu akan dipertahankan, tetapi masih bisa dimodifikasi. Jika Anda menggunakan "salin", salinan yang tidak dapat diubah akan dibuat ketika Anda menetapkan NSMutableString; sejak saat itu "salin" di properti hanya akan tetap, karena salinan string yang dapat berubah itu sendiri tidak dapat diubah.
gnasher729

1
Anda kehilangan beberapa fakta penting di sini, jika Anda menggunakan objek yang berasal dari variabel yang disimpan, ketika variabel itu diubah, begitu juga objek Anda, jika itu berasal dari variabel yang disalin, objek Anda akan memiliki nilai saat ini dari variabel yang tidak akan berubah
Bryan P

-1

Jika string sangat besar maka salinan akan mempengaruhi kinerja dan dua salinan string besar akan menggunakan lebih banyak memori.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.