Tài sản NSString: sao chép hay giữ lại?


331

Giả sử tôi có một lớp được gọi SomeClassvới stringtên thuộc tính:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Tôi hiểu rằng tên đó có thể được chỉ định NSMutableStringtrong trường hợp này có thể dẫn đến hành vi sai trái.

  • Đối với các chuỗi nói chung, luôn luôn là một ý tưởng tốt để sử dụng copythuộc tính thay vì retain?
  • Là một tài sản "sao chép" theo bất kỳ cách nào kém hiệu quả hơn một tài sản "giữ lại" như vậy?

6
Câu hỏi tiếp theo: Có nên namephát hành trong deallochay không?
Chetan

7
@chetan Đúng vậy!
Jon

Câu trả lời:


440

Đối với các thuộc tính có loại là một lớp giá trị bất biến phù hợp với NSCopyinggiao thức, bạn hầu như luôn luôn chỉ định copytrong @propertykhai báo của mình . Chỉ định retainlà điều bạn gần như không bao giờ muốn trong tình huống như vậy.

Đây là lý do tại sao bạn muốn làm điều đó:

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

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

[someName setString:@"Debajit"];

Giá trị hiện tại của Person.nametài sản sẽ khác nhau tùy thuộc vào việc tài sản được khai báo retainhay copy- nó sẽ là @"Debajit"nếu tài sản được đánh dấu retain, nhưng @"Chris"nếu tài sản được đánh dấu copy.

Vì trong hầu hết các trường hợp bạn muốn ngăn chặn việc làm biến đổi các thuộc tính của đối tượng đằng sau lưng của nó, bạn nên đánh dấu các thuộc tính đại diện cho chúng copy. (Và nếu bạn tự viết setter thay vì sử dụng, @synthesizebạn nên nhớ thực sự sử dụng copythay vì retaintrong đó.)


61
Câu trả lời này có thể đã gây ra một số nhầm lẫn (xem robnapier.net/blog/im Hiệning-nscopying-439 # com-1312 ). Bạn hoàn toàn chính xác về NSString, nhưng tôi tin rằng bạn đã đưa ra quan điểm hơi quá chung chung. Lý do NSString nên được sao chép là vì nó có một lớp con có thể thay đổi chung (NSMutableString). Đối với các lớp không có lớp con có thể thay đổi (đặc biệt là các lớp bạn tự viết), tốt hơn là giữ lại chúng thay vì sao chép để tránh lãng phí thời gian và bộ nhớ.
Rob Napier

63
Lý luận của bạn không chính xác. Bạn không nên quyết định sao chép hay giữ lại dựa trên thời gian / bộ nhớ, bạn nên xác định điều đó dựa trên ngữ nghĩa mong muốn. Đó là lý do tại sao tôi đặc biệt sử dụng thuật ngữ "lớp giá trị bất biến" trong câu trả lời của mình. Đây cũng không phải là vấn đề liệu một lớp có các lớp con có thể thay đổi hay là chính nó có thể thay đổi hay không.
Chris Hanson

10
Thật xấu hổ khi Obj-C không thể thực thi bất biến theo loại. Điều này giống như việc thiếu const transitive của C ++. Cá nhân tôi làm việc như thể các chuỗi luôn bất biến. Nếu tôi cần sử dụng một chuỗi có thể thay đổi, tôi sẽ không bao giờ đưa ra một tài liệu tham khảo bất biến nếu tôi có thể thay đổi nó sau này. Tôi sẽ coi bất cứ thứ gì khác là mùi mã. Kết quả là - trong mã của tôi (mà tôi làm việc một mình) tôi sử dụng giữ lại trên tất cả các chuỗi của mình. Nếu tôi làm việc như một phần của một đội, tôi có thể nhìn mọi thứ khác đi.
philsquared

5
@Phil Nash: Tôi coi đó là mùi mã để sử dụng các phong cách khác nhau cho các dự án bạn làm việc một mình và các dự án bạn chia sẻ với người khác. Trong mọi Ngôn ngữ / Khung có các quy tắc hoặc kiểu chung mà các nhà phát triển đồng ý. Bỏ qua chúng trong các dự án tư nhân có vẻ sai. Và với lý do của bạn "Trong mã của tôi, tôi không trả về các chuỗi có thể thay đổi": Điều đó có thể hoạt động cho các chuỗi của riêng bạn, nhưng bạn không bao giờ biết về các chuỗi bạn nhận được từ các khung.
Nikolai Ruhe

7
@Nikolai Tôi chỉ không sử dụng NSMutableString, ngoại trừ loại "trình tạo chuỗi" thoáng qua (từ đó tôi lập tức lấy một bản sao không thay đổi). Tôi muốn chúng là loại kín đáo - nhưng tôi sẽ cho phép thực tế rằng bản sao được tự do giữ lại nếu chuỗi ban đầu không thể thay đổi được phần lớn mối quan tâm của tôi.
philsquared

120

Sao chép nên được sử dụng cho NSString. Nếu nó là Mutable, thì nó sẽ được sao chép. Nếu không, thì nó sẽ được giữ lại. Chính xác là ngữ nghĩa mà bạn muốn trong một ứng dụng (hãy để loại làm tốt nhất).


1
Tôi vẫn muốn các hình thức có thể thay đổi và bất biến được kín đáo, nhưng tôi đã nhận ra trước đó bản sao đó có thể được giữ lại nếu chuỗi ban đầu là bất biến - hầu hết là ở đó. Cảm ơn.
philsquared

25
+1 để đề cập rằng NSStringtài sản được tuyên bố là copysẽ nhận được bằng retainmọi cách (tất nhiên nếu đó là bất biến). Ví dụ khác tôi có thể nghĩ là NSNumber.
matm

Sự khác biệt giữa câu trả lời này và câu trả lời được bình chọn bởi @GBY là gì?
Gary Lyn

67

Đối với các chuỗi nói chung, luôn luôn là một ý tưởng tốt để sử dụng thuộc tính sao chép thay vì giữ lại?

Có - nói chung luôn luôn sử dụng thuộc tính sao chép.

Điều này là do thuộc tính NSString của bạn có thể được truyền qua một thể hiện NSString hoặc một thể hiện NSMutableString , và do đó chúng tôi không thể thực sự xác định xem giá trị được truyền là một đối tượng bất biến hay có thể thay đổi.

Là một tài sản "sao chép" theo bất kỳ cách nào kém hiệu quả hơn một tài sản "giữ lại" như vậy?

  • Nếu tài sản của bạn đang được thông qua một phiên bản NSString , câu trả lời là " Không " - sao chép không hiệu quả hơn việc giữ lại.
    (Nó không kém hiệu quả vì NSString đủ thông minh để không thực sự thực hiện một bản sao.)

  • Nếu thuộc tính của bạn được thông qua một thể hiện NSMutableString thì câu trả lời là " " - sao chép kém hiệu quả hơn so với giữ lại.
    (Nó kém hiệu quả hơn vì phải cấp phát bộ nhớ và sao chép thực tế, nhưng đây có lẽ là một điều đáng mong đợi.)

  • Nói chung, một thuộc tính "được sao chép" có khả năng kém hiệu quả hơn - tuy nhiên thông qua việc sử dụng NSCopyinggiao thức, có thể thực hiện một lớp "sao cho hiệu quả" để sao chép. Các trường hợp NSString là một ví dụ về điều này.

Nói chung (không chỉ cho NSString), khi nào tôi nên sử dụng "bản sao" thay vì "giữ lại"?

Bạn nên luôn luôn sử dụng copykhi bạn không muốn trạng thái bên trong của tài sản thay đổi mà không có cảnh báo. Ngay cả đối với các đối tượng bất biến - các đối tượng bất biến được viết đúng sẽ xử lý sao chép hiệu quả (xem phần tiếp theo về tính bất biến và NSCopying).

Có thể có lý do hiệu năng cho retaincác đối tượng, nhưng nó đi kèm với chi phí bảo trì - bạn phải quản lý khả năng thay đổi trạng thái bên trong bên ngoài mã của mình. Như họ nói - tối ưu hóa cuối cùng.

Nhưng, tôi đã viết lớp học của mình là bất biến - tôi không thể "giữ lại" nó sao?

Không - sử dụng copy. Nếu lớp của bạn thực sự bất biến thì cách tốt nhất là thực hiện NSCopyinggiao thức để làm cho lớp của bạn trở lại chính nó khi copyđược sử dụng. Nếu bạn làm điều này:

  • Những người dùng khác trong lớp của bạn sẽ đạt được các lợi ích hiệu suất khi họ sử dụng copy.
  • Các copychú thích làm cho mã của riêng bạn dễ bảo trì hơn - các copychú thích chỉ ra rằng bạn thực sự không cần phải lo lắng về đối tượng này thay đổi trạng thái ở những nơi khác.

39

Tôi cố gắng tuân theo quy tắc đơn giản này:

  • Tôi có muốn giữ giá trị của đối tượng tại thời điểm khi tôi gán nó cho tài sản của mình không? Sử dụng bản sao .

  • Tôi có muốn giữ đối tượngtôi không quan tâm giá trị bên trong của nó hiện đang hoặc sẽ là gì trong tương lai? Sử dụng mạnh (giữ lại).

Để minh họa: Tôi muốn giữ cái tên "Lisa Miller" ( bản sao ) hay tôi muốn giữ lấy người Lisa Miller ( mạnh mẽ )? Tên của cô sau này có thể đổi thành "Lisa Smith", nhưng cô vẫn sẽ là cùng một người.


14

Thông qua ví dụ này, sao chép và giữ lại có thể được giải thích như sau:

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

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

[someName setString:@"Debajit"];

nếu tài sản thuộc loại bản sao thì,

một bản sao mới sẽ được tạo cho [Person name]chuỗi chứa nội dung của someNamechuỗi. Bây giờ bất kỳ hoạt động trên someNamechuỗi sẽ không có hiệu lực trên [Person name].

[Person name]someNamechuỗi sẽ có địa chỉ bộ nhớ khác nhau.

Nhưng trong trường hợp giữ lại,

cả hai [Person name]sẽ giữ cùng một địa chỉ bộ nhớ như của chuỗi somename, chỉ cần giữ lại số chuỗi somename sẽ được tăng thêm 1.

Vì vậy, bất kỳ thay đổi trong chuỗi somename sẽ được phản ánh trong [Person name]chuỗi.


3

Chắc chắn việc đặt 'bản sao' trên một tuyên bố thuộc tính bay lên khi sử dụng môi trường hướng đối tượng nơi các đối tượng trên heap được chuyển qua tham chiếu - một trong những lợi ích bạn nhận được ở đây là, khi thay đổi một đối tượng, tất cả các tham chiếu đến đối tượng đó xem những thay đổi mới nhất Rất nhiều ngôn ngữ cung cấp 'ref' hoặc các từ khóa tương tự để cho phép các loại giá trị (nghĩa là các cấu trúc trên ngăn xếp) được hưởng lợi từ cùng một hành vi. Cá nhân, tôi sử dụng bản sao một cách tiết kiệm và nếu tôi cảm thấy rằng giá trị thuộc tính cần được bảo vệ khỏi các thay đổi được thực hiện cho đối tượng được gán từ đó, tôi có thể gọi phương thức sao chép của đối tượng đó trong khi gán, ví dụ:

p.name = [someName copy];

Tất nhiên, khi thiết kế đối tượng có chứa thuộc tính đó, chỉ bạn mới biết liệu thiết kế có được lợi từ một mẫu mà bài tập lấy bản sao hay không - Cocoawithlove.com có những điều sau đây để nói:

"Bạn nên sử dụng một trình truy cập sao chép khi tham số setter có thể thay đổi nhưng bạn không thể thay đổi trạng thái bên trong của một thuộc tính mà không cần cảnh báo " - vì vậy, phán đoán về việc bạn có thể chịu được giá trị thay đổi bất ngờ hay không là của riêng bạn. Hãy tưởng tượng kịch bản này:

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

Trong trường hợp này, không sử dụng bản sao, đối tượng liên hệ của chúng tôi sẽ tự động nhận giá trị mới; tuy nhiên, nếu chúng tôi đã sử dụng nó, chúng tôi sẽ phải đảm bảo rằng các thay đổi được phát hiện và đồng bộ hóa theo cách thủ công. Trong trường hợp này, giữ lại ngữ nghĩa có thể là mong muốn; trong một bản khác, bản sao có thể phù hợp hơn.


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

Bạn nên sử dụng bản sao mọi lúc để khai báo thuộc tính NSString

@property (nonatomic, copy) NSString* name;

Bạn nên đọc những điều này để biết thêm thông tin về việc nó trả về chuỗi bất biến (trong trường hợp chuỗi có thể thay đổi được thông qua) hoặc trả về chuỗi được giữ lại (trong trường hợp chuỗi không thay đổi được thông qua)

Tham chiếu giao thức NSCopying

Triển khai NSCopying bằng cách giữ lại bản gốc thay vì tạo một bản sao mới khi lớp và nội dung của nó là bất biến

Đối tượng giá trị

Vì vậy, đối với phiên bản bất biến của chúng tôi, chúng tôi chỉ có thể làm điều này:

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

-1

Vì tên là một (không thay đổi) NSString, sao chép hoặc giữ lại không có sự khác biệt nếu bạn đặt tên khác NSStringthành tên. Nói cách khác, sao chép hành xử giống như giữ lại, tăng số lượng tham chiếu lên một. Tôi nghĩ rằng đó là một tối ưu hóa tự động cho các lớp bất biến, vì chúng là bất biến và không cần phải nhân bản. Nhưng khi a NSMutalbeString mstrđược đặt thành tên, nội dung của mstrsẽ được sao chép vì mục đích chính xác.


1
Bạn đang nhầm lẫn loại khai báo với loại thực tế. Nếu bạn sử dụng thuộc tính "giữ lại" và gán NSMutableString, NSMutableString đó sẽ được giữ lại, nhưng vẫn có thể được sửa đổi. Nếu bạn sử dụng "bản sao", một bản sao bất biến sẽ được tạo khi bạn gán NSMutableString; từ đó trở đi "sao chép" trên thuộc tính sẽ chỉ giữ lại, vì bản sao của chuỗi có thể thay đổi là chính nó không thay đổi.
gnasher729

1
Bạn đang thiếu một số sự kiện quan trọng ở đây, nếu bạn sử dụng một đối tượng đến từ biến được giữ lại, khi biến đó được sửa đổi, thì đối tượng của bạn, nếu nó đến từ một biến được sao chép, đối tượng của bạn sẽ có giá trị hiện tại của biến đó sẽ không thay đổi
Bryan P

-1

Nếu chuỗi rất lớn thì sao chép sẽ ảnh hưởng đến hiệu suất và hai bản sao của chuỗi lớn sẽ sử dụng nhiều bộ nhớ hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.