คุณสมบัติ NSString: คัดลอกหรือเก็บ?


331

สมมติว่าฉันได้เรียนที่เรียกว่าSomeClassมีstringชื่อคุณสมบัติ:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

ฉันเข้าใจว่าชื่ออาจได้รับมอบหมายNSMutableStringซึ่งในกรณีนี้อาจนำไปสู่พฤติกรรมที่ผิดพลาด

  • สำหรับสตริงโดยทั่วไปก็คือมักจะเป็นความคิดที่ดีที่จะใช้copyแอตทริบิวต์แทนretain?
  • คุณสมบัติ "คัดลอก" ในทางใดทางหนึ่งที่มีประสิทธิภาพน้อยกว่าคุณสมบัติ "เก็บรักษา -ed" หรือไม่?

6
คำถามติดตามผล: ควรnameเปิดตัวdeallocหรือไม่?
Chetan

7
@chetan ใช่มันควรจะเป็น!
Jon

คำตอบ:


440

สำหรับแอททริบิวต์ที่มีประเภทเป็นคลาสค่าที่ไม่เปลี่ยนรูปได้ซึ่งเป็นไปตามNSCopyingโปรโตคอลคุณควรระบุcopyใน@propertyการประกาศของคุณเกือบทุกครั้ง การระบุretainเป็นสิ่งที่คุณแทบไม่เคยต้องการในสถานการณ์เช่นนี้

นี่คือเหตุผลที่คุณต้องการทำเช่นนั้น:

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

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

[someName setString:@"Debajit"];

มูลค่าปัจจุบันของPerson.nameสถานที่ให้บริการจะแตกต่างกันขึ้นอยู่กับว่าทรัพย์สินที่มีการประกาศretainหรือcopy- จะเป็น@"Debajit"ถ้าคุณสมบัติถูกทำเครื่องหมายretainแต่ถ้าคุณสมบัติถูกทำเครื่องหมาย@"Chris"copy

เนื่องจากในเกือบทุกกรณีคุณต้องการ ป้องกันไม่ให้copyกรรมวิธีคุณลักษณะของวัตถุด้านหลังของคุณควรจะทำเครื่องหมายคุณสมบัติที่เป็นตัวแทนของพวกเขา (และถ้าคุณเขียน setter ด้วยตัวเองแทนที่จะใช้@synthesizeคุณควรจำไว้ว่าให้ใช้จริงcopyแทนการใช้retain)


61
คำตอบนี้อาจทำให้เกิดความสับสน (ดูrobnapier.net/blog/implementing-nscopying-439#comment-1312 ) คุณถูกต้องอย่างแน่นอนเกี่ยวกับ NSString แต่ฉันเชื่อว่าคุณทำให้ประเด็นนี้ค่อนข้างกว้างเกินไป เหตุผลที่ควรคัดลอก NSString คือมีคลาสย่อยที่ไม่แน่นอน (NSMutableString) สำหรับคลาสที่ไม่มีคลาสย่อยที่ไม่แน่นอน (โดยเฉพาะคลาสที่คุณเขียนด้วยตัวเอง) โดยปกติแล้วจะเป็นการดีกว่าที่จะเก็บไว้แทนที่จะคัดลอกเพื่อหลีกเลี่ยงการเสียเวลาและความจำ
Rob Napier

63
เหตุผลของคุณไม่ถูกต้อง คุณไม่ควรทำการตัดสินใจว่าจะคัดลอกหรือเก็บรักษาตามเวลา / หน่วยความจำหรือไม่คุณควรกำหนดตามความหมายที่ต้องการ นั่นเป็นเหตุผลที่ฉันใช้คำว่า "ระดับคุณค่าที่ไม่เปลี่ยนรูปแบบ" ในคำตอบของฉันโดยเฉพาะ มันก็ไม่สำคัญว่าคลาสจะมีคลาสย่อยที่ไม่แน่นอนหรือไม่แน่นอน
Chris Hanson

10
มันเป็นความอัปยศที่ Obj-C ไม่สามารถบังคับใช้ไม่ได้ตามประเภท นี่เป็นเช่นเดียวกับการขาด c ++ ของ transitive ส่วนตัวฉันทำงานราวกับว่าสายไม่เปลี่ยนรูปเสมอ หากฉันจำเป็นต้องใช้สตริงที่เปลี่ยนแปลงได้ฉันจะไม่แจกการอ้างอิงที่ไม่เปลี่ยนรูปถ้าฉันอาจกลายพันธุ์ในภายหลัง ฉันคิดว่าอะไรที่แตกต่างกันเพื่อให้ได้กลิ่นรหัส ดังนั้นในรหัสของฉัน (ที่ฉันทำงานคนเดียว) ฉันใช้เก็บไว้ในทุกสายของฉัน ถ้าฉันทำงานเป็นส่วนหนึ่งของทีมฉันอาจมองสิ่งที่แตกต่าง
philsquared

5
@Phil Nash: ฉันคิดว่ามันเป็นกลิ่นของรหัสในการใช้สไตล์ที่แตกต่างสำหรับโครงการที่คุณทำงานคนเดียวและโครงการที่คุณแบ่งปันกับผู้อื่น ในทุกภาษา / กรอบงานมีกฎหรือสไตล์ทั่วไปที่นักพัฒนาเห็นด้วย การไม่สนใจพวกเขาในโครงการส่วนตัวดูเหมือนผิด และสำหรับเหตุผลของคุณ "ในรหัสของฉันฉันจะไม่คืนสตริงที่ไม่แน่นอน": นั่นอาจใช้ได้กับสตริงของคุณ แต่คุณไม่เคยรู้เกี่ยวกับสตริงที่คุณได้รับจากเฟรมเวิร์ก
Nikolai Ruhe

7
@ Nikolai ฉันไม่ได้ใช้NSMutableStringยกเว้นเป็นชนิด "ตัวสร้างสตริง" ชั่วคราว (ซึ่งฉันจะคัดลอกไม่เปลี่ยนรูปทันที) ฉันต้องการให้พวกเขาเป็นประเภทที่รอบคอบ - แต่ฉันจะอนุญาตให้ความจริงที่ว่าสำเนามีอิสระที่จะทำการเก็บรักษาถ้าสายเดิมไม่สามารถแก้ไขได้ลดความกังวลส่วนใหญ่ของฉัน
philsquared

120

ควรใช้การคัดลอกสำหรับ NSString ถ้ามันไม่แน่นอนมันจะถูกคัดลอก หากไม่เป็นเช่นนั้นก็จะได้รับการเก็บรักษาไว้ ความหมายที่แน่นอนที่คุณต้องการในแอพ (ปล่อยให้ประเภททำในสิ่งที่ดีที่สุด)


1
ฉันยังคงต้องการรูปแบบที่ไม่แน่นอนและไม่เปลี่ยนรูปแบบที่จะรอบคอบ แต่ฉันไม่ได้ตระหนักก่อนที่คัดลอกนั้นอาจถูกเก็บไว้ถ้าสายเดิมไม่เปลี่ยนรูป - ซึ่งเป็นวิธีที่มีมากที่สุด ขอบคุณ
philsquared

25
+1 สำหรับการกล่าวถึงNSStringคุณสมบัติที่ประกาศว่าcopyจะได้รับretainต่อไป (ถ้าไม่แน่นอนไม่เปลี่ยน) ตัวอย่างอื่น ๆ NSNumberฉันจะคิดว่ามี
matm

ความแตกต่างระหว่างคำตอบนี้กับคำว่า down โหวตโดย @GBY คืออะไร
Gary Lyn

67

สำหรับสตริงโดยทั่วไปคุณควรใช้แอตทริบิวต์ copy แทนที่จะเก็บไว้หรือไม่

ใช่ - โดยทั่วไปให้ใช้แอตทริบิวต์การคัดลอกเสมอ

นี่เป็นเพราะคุณสมบัติ NSStringของคุณสามารถส่งผ่านอินสแตนซ์ NSStringหรืออินสแตนซ์NSMutableStringและดังนั้นเราจึงไม่สามารถระบุได้ว่าค่าที่ส่งผ่านนั้นเป็นวัตถุที่ไม่เปลี่ยนรูปหรือไม่แน่นอน

คุณสมบัติ "คัดลอก" ในทางใดทางหนึ่งที่มีประสิทธิภาพน้อยกว่าคุณสมบัติ "เก็บรักษา -ed" หรือไม่?

  • ถ้าคุณสมบัติของคุณกำลังถูกส่งผ่านอินสแตนซ์ NSStringคำตอบคือ " ไม่ " - การคัดลอกไม่ได้มีประสิทธิภาพน้อยกว่าเก็บ
    (มันไม่ได้มีประสิทธิภาพน้อยลงเพราะ NSString ฉลาดพอที่จะไม่ทำการคัดลอก)

  • หากคุณสมบัติของคุณผ่านอินสแตนซ์ NSMutableStringคำตอบคือ " ใช่ " - การคัดลอกมีประสิทธิภาพน้อยกว่าการเก็บ
    (มีประสิทธิภาพน้อยกว่าเนื่องจากการจัดสรรหน่วยความจำและการคัดลอกจริงจะต้องเกิดขึ้น แต่นี่อาจเป็นสิ่งที่ต้องการ)

  • โดยทั่วไปการพูดคุณสมบัติ "คัดลอก" มีศักยภาพที่จะมีประสิทธิภาพน้อยลง - อย่างไรก็ตามผ่านการใช้NSCopyingโปรโตคอลคุณสามารถใช้คลาสที่ "มีประสิทธิภาพ" เพื่อคัดลอกตามที่จะรักษาไว้ ตัวอย่างของ NSStringเป็นตัวอย่างของสิ่งนี้

โดยทั่วไป (ไม่ใช่แค่สำหรับ NSString) ฉันควรใช้ "คัดลอก" แทน "เก็บ" เมื่อใด

คุณควรใช้copyเมื่อคุณไม่ต้องการให้สถานะภายในของการเปลี่ยนแปลงคุณสมบัติโดยไม่มีการเตือน แม้แต่วัตถุที่ไม่เปลี่ยนรูป - วัตถุที่ไม่เปลี่ยนรูปที่เขียนอย่างถูกต้องจะจัดการสำเนาได้อย่างมีประสิทธิภาพ (ดูหัวข้อถัดไปเกี่ยวกับการไม่เปลี่ยนรูปและNSCopying)

อาจมีเหตุผลด้านประสิทธิภาพการทำงานกับretainวัตถุ แต่มันมาพร้อมกับค่าใช้จ่ายในการบำรุงรักษา - คุณต้องจัดการความเป็นไปได้ของการเปลี่ยนแปลงสถานะภายในนอกรหัสของคุณ ตามที่พวกเขาพูด - เพิ่มประสิทธิภาพล่าสุด

แต่ฉันเขียนชั้นเรียนของฉันให้ไม่เปลี่ยนรูปฉันไม่สามารถ "เก็บ" หรือไม่?

- copyไม่มีการใช้งาน หากคลาสของคุณไม่เปลี่ยนรูปจริง ๆ คุณควรใช้NSCopyingโพรโทคอลเพื่อให้คลาสของคุณกลับคืนมาเมื่อcopyใช้ หากคุณทำสิ่งนี้:

  • ผู้ใช้อื่น ๆ copyของชั้นเรียนของคุณจะได้รับผลประโยชน์เมื่อพวกเขาใช้
  • copyคำอธิบายประกอบทำให้รหัสของคุณเองการบำรุงรักษามากขึ้น - copyคำอธิบายประกอบบ่งบอกว่าคุณจริงๆไม่จำเป็นต้องกังวลเกี่ยวกับวัตถุนี้มีการเปลี่ยนแปลงอื่น ๆ ของรัฐ

39

ฉันพยายามที่จะปฏิบัติตามกฎง่ายๆนี้:

  • ฉันต้องการที่จะยึดมั่นกับมูลค่าของวัตถุณ เวลาที่ฉันกำหนดให้กับทรัพย์สินของฉัน? ใช้สำเนา

  • ฉันต้องการที่จะยึดมั่นในวัตถุและฉันไม่สนใจว่าค่าภายในของมันในปัจจุบันหรือในอนาคต? ใช้แรง (เก็บ)

เพื่ออธิบาย: ฉันต้องการที่จะยึดมั่นในชื่อ "Lisa Miller" ( สำเนา ) หรือฉันต้องการที่จะยึดมั่นกับบุคคลที่ Lisa Miller ( แข็งแรง ) หรือไม่ ชื่อของเธออาจเปลี่ยนเป็น "Lisa Smith" ในภายหลัง แต่เธอจะยังคงเป็นบุคคลเดียวกัน


14

ผ่านตัวอย่างการคัดลอกและเก็บรักษาสามารถอธิบายได้เช่น:

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

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

[someName setString:@"Debajit"];

ถ้าคุณสมบัติเป็นของการคัดลอกประเภทแล้ว

สำเนาใหม่จะถูกสร้างขึ้นสำหรับ[Person name]สตริงที่จะเก็บเนื้อหาของsomeNameสตริง ตอนนี้การดำเนินการใด ๆ เกี่ยวกับสตริงจะไม่มีผลกระทบต่อsomeName[Person name]

[Person name]และsomeNameสตริงจะมีที่อยู่หน่วยความจำที่แตกต่างกัน

แต่ในกรณีที่รักษาไว้

ทั้งสอง[Person name]จะเก็บที่อยู่หน่วยความจำเดียวกันของสตริง somename เพียงจำนวนที่เก็บไว้ของ somename สตริงจะเพิ่มขึ้น 1

ดังนั้นการเปลี่ยนแปลงใด ๆ ในสตริง somename จะปรากฏใน[Person name]สตริง


3

การวาง 'คัดลอก' ลงในประกาศคุณสมบัติจะต้องเผชิญกับการใช้สภาพแวดล้อมเชิงวัตถุซึ่งวัตถุบนฮีปจะถูกส่งผ่านโดยการอ้างอิง - ประโยชน์อย่างหนึ่งที่คุณได้รับจากที่นี่คือเมื่อเปลี่ยนวัตถุการอ้างอิงทั้งหมดไปยังวัตถุนั้น ดูการเปลี่ยนแปลงล่าสุด มีภาษาจำนวนมากที่ให้ 'อ้างอิง' หรือคำหลักที่คล้ายกันเพื่อให้ประเภทค่า (เช่นโครงสร้างในสแต็ก) ได้รับประโยชน์จากพฤติกรรมเดียวกัน โดยส่วนตัวฉันจะใช้การทำสำเนาเท่าที่ควรและถ้าฉันรู้สึกว่าค่าคุณสมบัติควรได้รับการปกป้องจากการเปลี่ยนแปลงที่เกิดขึ้นกับวัตถุที่ได้รับมอบหมายฉันสามารถเรียกวิธีการคัดลอกของวัตถุนั้นในระหว่างการมอบหมายเช่น:

p.name = [someName copy];

แน่นอนเมื่อออกแบบวัตถุที่มีคุณสมบัตินั้นมีเพียงคุณเท่านั้นที่จะรู้ว่าการออกแบบได้ประโยชน์จากรูปแบบที่ได้รับมอบหมายถ่ายสำเนาหรือไม่ - Cocoawithlove.comมีดังต่อไปนี้ว่า

"คุณควรใช้ copy accessor เมื่อพารามิเตอร์ setter อาจไม่แน่นอนแต่คุณไม่สามารถมีสถานะภายในของการเปลี่ยนแปลงคุณสมบัติโดยไม่มีการเตือน " - ดังนั้นการตัดสินว่าคุณสามารถยืนค่าการเปลี่ยนแปลงโดยไม่ได้ตั้งใจได้หรือไม่ ลองนึกภาพสถานการณ์นี้:

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

ในกรณีนี้โดยไม่ใช้การคัดลอกวัตถุผู้ติดต่อของเราจะรับค่าใหม่โดยอัตโนมัติ ถ้าเราใช้มันเราจะต้องตรวจสอบให้แน่ใจว่าตรวจพบและซิงค์การเปลี่ยนแปลงด้วยตนเอง ในกรณีนี้คงความหมายไว้อาจเป็นที่ต้องการ ในอีกสำเนาอาจเหมาะสมกว่า


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

คุณควรใช้การคัดลอกตลอดเวลาเพื่อประกาศคุณสมบัติ NSString

@property (nonatomic, copy) NSString* name;

คุณควรอ่านสิ่งเหล่านี้สำหรับข้อมูลเพิ่มเติมว่ามันส่งคืนสตริงที่ไม่เปลี่ยนรูปแบบหรือไม่ (ในกรณีที่สตริงที่เปลี่ยนแปลงไม่ได้ถูกส่งผ่าน) หรือส่งคืนสตริงที่ถูกเก็บรักษาไว้ (ในกรณีที่สตริงที่เปลี่ยนไม่ได้

การอ้างอิงโปรโตคอล NSCopying

ใช้ NSCopying โดยเก็บต้นฉบับไว้แทนที่จะสร้างสำเนาใหม่เมื่อคลาสและเนื้อหาไม่เปลี่ยนแปลง

วัตถุค่า

ดังนั้นสำหรับเวอร์ชั่นที่ไม่เปลี่ยนรูปของเราเราสามารถทำสิ่งนี้

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

-1

เนื่องจากชื่อคือ (ไม่เปลี่ยนรูป) การNSStringคัดลอกหรือเก็บรักษาจึงไม่แตกต่างกันหากคุณตั้งNSStringชื่อใหม่ กล่าวอีกนัยหนึ่งการคัดลอกจะทำงานเหมือนเก็บไว้เพิ่มจำนวนการอ้างอิงทีละหนึ่ง ฉันคิดว่าเป็นการเพิ่มประสิทธิภาพโดยอัตโนมัติสำหรับคลาสที่ไม่เปลี่ยนรูปเนื่องจากพวกเขาไม่เปลี่ยนรูปแบบและไม่จำเป็นต้องถูกโคลน แต่เมื่อNSMutalbeString mstrตั้งค่าเป็นชื่อเนื้อหาของmstrจะถูกคัดลอกเพื่อความถูกต้อง


1
คุณกำลังสับสนประเภทที่ประกาศด้วยประเภทจริง หากคุณใช้คุณสมบัติ "เก็บรักษา" และกำหนด NSMutableString นั้น NSMutableString นั้นจะถูกเก็บไว้ แต่ยังคงสามารถแก้ไขได้ หากคุณใช้ "คัดลอก" สำเนาที่ไม่เปลี่ยนรูปจะถูกสร้างขึ้นเมื่อคุณกำหนด NSMutableString จากนั้นใน "คัดลอก" ในทรัพย์สินจะเพียงเก็บไว้เพราะสำเนาของสตริงที่ไม่แน่นอนเป็นตัวเองไม่เปลี่ยนรูป
gnasher729

1
คุณพลาดข้อเท็จจริงสำคัญบางอย่างที่นี่ถ้าคุณใช้วัตถุที่มาจากตัวแปรที่เก็บไว้เมื่อตัวแปรนั้นได้รับการแก้ไขวัตถุของคุณก็เช่นกันถ้ามันมาจากตัวแปรที่คัดลอกวัตถุของคุณจะมีค่าปัจจุบันของตัวแปรที่ จะไม่เปลี่ยน
ไบรอัน P

-1

หากสตริงมีขนาดใหญ่มากการคัดลอกจะส่งผลกระทบต่อประสิทธิภาพการทำงานและสองสำเนาของสตริงขนาดใหญ่จะใช้หน่วยความจำมากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.