Властивість NSString: копіювати чи зберігати?


331

Скажімо , у мене є клас з ім'ям SomeClassз stringім'ям властивості:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Я розумію, що ім'я може бути призначене NSMutableStringв такому випадку, це може призвести до помилкової поведінки.

  • Чи завжди для рядків завжди корисно використовувати copyатрибут замість retain?
  • Чи властивість "скопійовано" якось менш ефективною, ніж така "збережена редакція" властивості?

6
Подальше запитання: слід nameзвільнятись deallocчи ні?
Четан

7
@chetan Так, це повинно!
Джон

Відповіді:


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. (І якщо ви пишете сетер самостійно, а не використовуєте, @synthesizeви повинні пам’ятати, що насправді використовувати copyзамість retainнього.)


61
Ця відповідь, можливо, викликала певну плутанину (див. Robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Ви абсолютно правильні щодо NSString, але я вважаю, що ви зробили пункт занадто загальним. Причина NSString повинна бути скопійована в тому, що вона має загальний підклас, що змінюється (NSMutableString). Для класів, які не мають змінного підкласу (особливо класів, які ви пишете самі), зазвичай краще зберігати їх, а не копіювати, щоб уникнути втрати часу та пам'яті.
Роб Нап'єр

63
Ваші міркування невірні. Ви не повинні визначати, чи потрібно копіювати чи зберігати на основі часу / пам'яті, ви повинні визначати це, виходячи з потрібної семантики. Ось чому я спеціально використовував у своїй відповіді термін "незмінний ціннісний клас". Це також не питання про те, чи має клас змінні підкласи, чи сам він змінюється.
Кріс Гансон,

10
Прикро, що Obj-C не може забезпечити незмінність за типом. Це те саме, що у C ++ відсутність транзитивних const. Особисто я працюю так, ніби струни завжди незмінні. Якщо мені коли-небудь потрібно використовувати рядок, що змінюється, я ніколи не роздам непорушну посилання, якщо пізніше я можу її змінити Я б вважав, що все інше є кодовим запахом. Як результат - у своєму коді (над яким я працюю поодинці) я використовую утримувати на всіх своїх рядках. Якби я працював у складі команди, я би міг поглянути інакше.
фільтрований

5
@Phil Nash: Я вважаю кодовим запахом використання різних стилів для проектів, над якими ви працюєте поодинці та проектів, якими ви ділитесь з іншими. У кожній мові / рамках існують загальні правила чи стилі, з якими погоджуються розробники. Нехтувати ними в приватних проектах здається неправильним. А для вашого обґрунтування "У моєму коді я не повертаю змінні рядки": це може працювати для ваших власних рядків, але ви ніколи не знаєте про рядки, які отримуєте з фреймворків.
Микола Рухе

7
@Nikolai я просто не використовую NSMutableString, за винятком перехідного типу "builder string" (з якого негайно беру незмінну копію). Я вважаю за краще, щоб вони були стриманими типами, але я дозволю, що факт, що копія вільний, можна зберегти, якщо оригінальна рядок не змінюється, зменшує більшу частину моєї стурбованості.
фільтрований

120

Копію слід використовувати для NSString. Якщо він є мутаційним, він копіюється. Якщо ні, то вона просто зберігається. Саме семантику, яку ви хочете в додатку (нехай тип робить найкраще).


1
Я б все-таки вважав за краще, щоб змінні та незмінні форми були стримані, але я раніше не розумів, що ця копія може просто зберегтись, якщо оригінальний рядок є незмінним - що є більшою частиною шляху. Дякую.
фільтрований

25
+1 для згадування про те, що NSStringвластивість, оголошене як copy, retainвсе одно буде отримано (звичайно, якщо воно буде непорушним). Інший приклад, про який я можу придумати, - це NSNumber.
Матм

Яка різниця між цією відповіддю і проти, яку проголосував @GBY?
Gary Lyn

67

Чи завжди для рядків завжди корисно використовувати атрибут copy замість збереження?

Так - загалом завжди використовуйте атрибут copy.

Це тому, що ваше властивість NSString може передаватися екземпляру NSString або екземпляру NSMutableString , і тому ми не можемо реально визначити, чи передане значення є незмінним або змінним об'єктом.

Чи властивість "скопійовано" якось менш ефективною, ніж така "збережена редакція" властивості?

  • Якщо ваш ресурс передається екземпляру NSString , відповідь " Ні " - копіювання не менш ефективно, ніж зберегти.
    (Це не менш ефективно, оскільки NSString досить розумний, щоб фактично не виконувати копію.)

  • Якщо ваша власність передана екземпляру NSMutableString, то відповідь " Так " - копіювання є менш ефективним, ніж зберігати.
    (Це менш ефективно, оскільки має відбуватися власне розподілення пам'яті та копіювання, але це, мабуть, бажано.)

  • Взагалі кажучи, "скопійоване" властивість може бути менш ефективною, проте, використовуючи NSCopyingпротокол, можна реалізувати клас, який "настільки ж ефективний" для копіювання, як і для збереження. Приклади цього є NSString .

Як правило (не лише для NSString), коли я повинен використовувати "копію" замість "зберегти"?

Ви завжди повинні використовувати, copyколи не хочете, щоб внутрішній стан властивості змінювався без попередження. Навіть для незмінних об'єктів - правильно написані незмінні об’єкти будуть ефективно працювати з копією (див. Наступний розділ щодо незміненості та NSCopying).

У retainоб’єктів можуть бути причини продуктивності , але це пов'язано з накладними витратами на обслуговування - ви повинні керувати можливістю зміни внутрішнього стану поза кодом. Як кажуть - оптимізуйте останні.

Але я написав свій клас непорушним - чи не можу я просто «зберегти» його?

Ні - використовувати copy. Якщо ваш клас справді непорушний, то найкраще застосовувати NSCopyingпротокол, щоб ваш клас повертався сам при copyйого використанні. Якщо ви це зробите:

  • Інші користувачі вашого класу отримають переваги від продуктивності під час їх використання copy.
  • copyАнотацій робить свій власний код більш ремонтопрігодни - copyанотацію вказує на те, що ви дійсно не потрібно турбуватися про цей об'єкт мінливого стану в іншому місці.

39

Я намагаюся дотримуватися цього простого правила:

  • Чи хочу я утримуватись за значенням об’єкта в той момент, коли я присвоюю його своєму майну? Використовуйте копію .

  • Чи хочу я триматися за об'єкт, і мені байдуже, які його внутрішні значення наразі є чи будуть у майбутньому? Використовуйте сильний (утримуйте).

Для ілюстрації: чи хочу я триматися за ім'я "Ліза Міллер" ( копія ) або хочу триматися за людину Лізу Міллер ( сильна )? Її ім’я згодом може змінитися на "Ліза Сміт", але вона все одно буде тією ж людиною.


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, просто кількість рахунків рядка імен буде збільшено на 1.

Отже, будь-яка зміна рядка somename відображатиметься в [Person name]рядку.


3

Безумовно, надягаючи "копію" на декларацію про властивості, лежить перед використанням об'єктно-орієнтованого середовища, коли об'єкти на купі передаються посиланням - одна з переваг, яку ви отримуєте тут, полягає в тому, що при зміні об'єкта всі посилання на цей об'єкт переглянути останні зміни. Багато мов надають «ref» або подібні ключові слова, щоб типи значень (тобто структури на стеку) отримували переваги від тієї ж поведінки. Особисто я скопіював копію, і якщо я відчув, що значення властивості слід захищати від змін, внесених до об'єкта, якому він був призначений, я міг би викликати метод копіювання цього об’єкта під час призначення, наприклад:

p.name = [someName copy];

Звичайно, розробляючи об'єкт, який містить цю властивість, ви тільки знатимете, чи дизайн виграє від шаблону, коли завдання отримують копії - Cocoawithlove.com має сказати наступне:

"Ви повинні використовувати аксесуар для копіювання, коли параметр 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;

Ви повинні прочитати їх для отримання додаткової інформації про те, повертає він незмінний рядок (у разі передачі змінної рядка) чи повертає збережену рядок (у разі передачі змінної рядки)

Посилання на протокол копіювання NSC

Реалізуйте NSCopying, зберігаючи оригінал замість створення нової копії, коли клас та його вміст є незмінними

Ціннісні об’єкти

Отже, для нашої непорушної версії ми можемо просто зробити це:

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

-1

Оскільки ім’я є (незмінним) NSString, копіювання або збереження не має значення, якщо ви встановите інше NSStringім'я. Іншим словом, копія поводиться так само, як і зберігати, збільшуючи кількість посилань на одиницю. Я думаю, що це автоматична оптимізація для непорушних класів, оскільки вони незмінні і не потребують клонування. Але коли а NSMutalbeString mstrвстановлено ім'я, зміст mstrбуде скопійовано заради коректності.


1
Ви плутаєте оголошений тип з фактичним типом. Якщо ви використовуєте властивість "retain" та призначите NSMutableString, то NSMutableString буде збережено, але все ще може бути змінено. Якщо ви використовуєте "копію", при призначенні NSMutableString буде створена незмінна копія; з цього моменту "копіювати" на властивість буде просто збережено, тому що сама копія змінного рядка є незмінною.
gnasher729

1
Тут ви не вистачаєте важливих фактів, якщо ви використовуєте об'єкт, що з’явився із збереженої змінної, коли ця змінна змінюється, як і ваш об’єкт, якщо він прийшов із скопійованої змінної, ваш об’єкт матиме поточне значення змінної, яка не зміниться
Брайан П

-1

Якщо рядок дуже великий, то копія вплине на продуктивність, і дві копії великої струни будуть використовувати більше пам'яті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.