Свойство 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 ++ отсутствие транзитивного конста. Лично я работаю так, как будто строки всегда неизменны. Если мне когда-нибудь понадобится использовать изменяемую строку, я никогда не раздам ​​неизменную ссылку, если смогу ее позже изменить. Я бы посчитал что-то другое запахом кода. В результате - в моем коде (над которым я работаю один) я использую retain для всех моих строк. Если бы я работал как часть команды, я мог бы смотреть на вещи по-другому.
philsquared

5
@Phil Nash: я бы посоветовал использовать разные стили для проектов, над которыми вы работаете в одиночку, и проектов, которыми вы делитесь с другими. В каждом языке / среде есть общие правила или стили, с которыми согласны разработчики. Игнорирование их в частных проектах кажется неправильным. И для вашего обоснования «В моем коде я не возвращаю изменяемые строки»: это может работать для ваших собственных строк, но вы никогда не знаете о строках, которые вы получаете из каркасов.
Николай Рюэ

7
@Nikolai Я просто не использую NSMutableString, за исключением временного типа «строителя строк» ​​(из которого я немедленно беру неизменную копию). Я бы предпочел, чтобы они были дискретными типами - но я позволю, чтобы тот факт, что копия является бесплатной, сохранит ее, если исходная строка не будет изменяемой, уменьшит большую часть моей озабоченности.
philsquared

120

Копия должна использоваться для NSString. Если он изменчив, он копируется. Если это не так, то это просто сохраняется. Именно семантика, которую вы хотите в приложении (пусть тип делает то, что лучше).


1
Я все еще предпочел бы, чтобы изменяемые и неизменяемые формы были незаметными, но я не знал, прежде чем эту копию можно будет просто сохранить, если исходная строка неизменна, что и происходит в большинстве случаев. Спасибо.
philsquared

25
+1 за упоминание того, что NSStringсвойство, объявленное как copy, все retainравно получит (если оно неизменное, конечно). Другой пример, который я могу придумать NSNumber.
MATM

Какая разница между этим ответом и ответом @GBY, за которого проголосовали "за"?
Гэри Лин

67

Для строк в целом всегда полезно использовать атрибут copy вместо retain?

Да, вообще всегда используйте атрибут копирования.

Это связано с тем, что вашему свойству NSString можно передать экземпляр NSString или экземпляр NSMutableString , и поэтому мы не можем реально определить, является ли передаваемое значение неизменным или изменяемым объектом.

Является ли «скопированное» свойство каким-либо образом менее эффективным, чем такое «сохраняемое» свойство?

  • Если вашему свойству передается экземпляр NSString , ответ « Нет » - копирование не менее эффективно, чем сохранение.
    (Это не менее эффективно, потому что NSString достаточно умен, чтобы фактически не выполнять копирование.)

  • Если вашему свойству передается экземпляр NSMutableString, тогда ответ « Да » - копирование менее эффективно, чем сохранение.
    (Это менее эффективно, потому что фактическое выделение памяти и копирование должны произойти, но это, вероятно, желательно.)

  • Вообще говоря, «скопированное» свойство потенциально может быть менее эффективным - однако, используя NSCopyingпротокол, можно реализовать класс, который «столь же эффективен» для копирования, как и для сохранения. NSString экземпляры являются примером этого.

Вообще (не только для NSString), когда я должен использовать «copy» вместо «retain»?

Вы должны всегда использовать, 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"];

если свойство имеет тип copy, то

новая [Person name]строка будет создана для строки, которая будет содержать содержимое someNameстроки. Теперь любая операция со someNameстрокой не будет влиять на [Person name].

[Person name]и someNameстроки будут иметь разные адреса памяти.

Но в случае сохранения,

оба [Person name]будут иметь тот же адрес памяти, что и строка соменам, просто число сохраненных строк соменам будет увеличено на 1.

Таким образом, любое изменение в строке somename будет отражено в [Person name]строке.


3

Безусловно, добавление «copy» к объявлению свойства противоречит использованию объектно-ориентированной среды, в которой объекты в куче передаются по ссылке - одно из преимуществ, которые вы получаете, заключается в том, что при изменении объекта все ссылки на этот объект увидеть последние изменения. Многие языки предоставляют «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

Вы должны использовать copy все время, чтобы объявить свойство NSString

@property (nonatomic, copy) NSString* name;

Вы должны прочитать их для получения дополнительной информации о том, возвращает ли она неизменяемую строку (в случае, если изменяемая строка была передана) или возвращает сохраненную строку (в случае, если неизменная строка была передана)

NSCopying Protocol Reference

Реализовать 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.