nsstring свойство: скопировать или сохранить?

<p>допустим, у меня есть класс с именем <code>someclass</code> с именем свойства <code>string</code>:</p><pre><code>@interface someclass : nsobject { nsstring* name; } @property (nonatomic, retain) nsstring* name; @end </code></pre><p>я понимаю, что имени может быть присвоено <code>nsmutablestring</code>, и в этом случае это может привести к ошибочному поведению.</p><ul> <li>для строк в целом: <em>всегда</em> хорошая ли идея использовать атрибут <code>copy</code> вместо <code>retain</code>?</li> <li>является ли "скопированное" свойство каким-либо образом менее эффективным, чем такое "сохраняемое" свойство?</li> </ul>

<p>для атрибутов, тип которых является классом неизменяемых значений, который соответствует протоколу <code>nscopying</code>, вы почти всегда должны указывать <code>copy</code> в своем объявлении <code>@property</code>. указание <code>retain</code> - это то, что вы почти никогда не хотите в такой ситуации.</p><p>вот почему вы хотите сделать это:</p><pre><code>nsmutablestring *somename = [nsmutablestring stringwithstring:@"chris"]; person *p = [[[person alloc] init] autorelease]; p.name = somename; [somename setstring:@"debajit"]; </code></pre><p>текущее значение свойства <code>person.name</code> будет отличаться в зависимости от того, объявлено ли свойство <code>retain</code> или <code>copy</code> - это будет <code>@"debajit"</code>, если свойство помечено <code>retain</code>, но <code>@"chris"</code>, если свойство помечено <code>copy</code>.</p><p>поскольку почти во всех случаях вы хотите <em>предотвратить</em> поменять атрибуты объекта за его спиной, вы должны пометить свойства, представляющие их <code>copy</code>. (и если вы пишете установщик самостоятельно, а не используете <code>@synthesize</code>, вы должны помнить, чтобы фактически использовать <code>copy</code> вместо <code>retain</code> в нем.)</p>

<p><strong>копировать</strong> должен использоваться для nsstring. если он изменчив, то он копируется. если это не так, то это просто сохраняется. именно семантика, которую вы хотите в приложении (пусть тип делает то, что лучше).</p>

<blockquote> <h3>для строк в целом всегда ли полезно использовать атрибут copy вместо retain?</h3> </blockquote><p><strong>да - обычно всегда используйте атрибут копирования.</strong></p><p>это потому, что вашему <strong><em> свойству nsstring </em></strong> можно передать <strong><em> экземпляр nsstring </em></strong> или <strong><em> экземпляр nsmutablestring </em></strong>, и поэтому мы не можем определить, действительно ли передаваемое значение является неизменным или изменяемым объектом.</p><blockquote> <h3>является ли "скопированное" свойство каким-либо образом менее эффективным, чем такое "сохраняемое" свойство?</h3> </blockquote><ul> <li> <p>если вашей собственности передается <strong><em> экземпляр nsstring </em></strong>, ответ "<strong>нет</strong>" - копирование не менее эффективно, чем сохранить.<br> <em> (это не менее эффективно, потому что nsstring достаточно умен, чтобы фактически не выполнять копирование.) </em></p> </li> <li> <p>если вашему свойству передан <strong><em> экземпляр nsmutablestring </em></strong>, тогда ответ "<strong>да</strong>" - копирование менее эффективно, чем сохранить.<br> <em> (это менее эффективно, потому что должно произойти фактическое выделение памяти и копирование, но это, вероятно, желательно.) </em></p> </li> <li> <p>вообще говоря, "скопированное" свойство потенциально может быть менее эффективным - однако с помощью протокола <code>nscopying</code> можно реализовать класс, который "столь же эффективен" для копирования, как и для сохранения. <strong><em> nsstring экземпляры </em></strong> являются примером этого.</p> </li> </ul><blockquote> <h3>вообще (не только для nsstring), когда я должен использовать "copy" вместо "retain"?</h3> </blockquote><p>вы всегда должны использовать <code>copy</code>, если вы не хотите, чтобы внутреннее состояние свойства изменялось без предупреждения. даже для неизменяемых объектов - правильно написанные неизменяемые объекты будут эффективно обрабатывать копии (см. следующий раздел об неизменяемости и <code>nscopying</code>).</p><p>для объектов <code>retain</code> могут быть причины производительности, но это связано с дополнительными затратами на обслуживание - вы должны управлять возможностью изменения внутреннего состояния вне вашего кода. как говорится - оптимизируй последний.</p><blockquote> <h3>но я написал свой класс, чтобы он был неизменным - я не могу просто "сохранить" его?</h3> </blockquote><p>нет - используйте <code>copy</code>. если ваш класс действительно неизменяемый, то лучше всего реализовать протокол <code>nscopying</code>, чтобы ваш класс возвращал сам себя, когда используется <code>copy</code>. если вы делаете это:</p><ul> <li>другие пользователи вашего класса получат преимущества в производительности, если они будут использовать <code>copy</code>.</li> <li>аннотация <code>copy</code> делает ваш собственный код более понятным - аннотация <code>copy</code> указывает на то, что вам действительно не нужно беспокоиться об изменении состояния этого объекта в другом месте.</li> </ul>

<p>я стараюсь следовать этому простому правилу:</p><ul> <li> <p>хочу ли я удерживать <strong>значение</strong> объекта <strong>в тот момент, когда я его назначаю</strong> моей собственности? используйте <strong>копия</strong>.</p> </li> <li> <p>хочу ли я удерживать <strong>объект</strong> и <strong>мне все равно, каковы его внутренние значения</strong> в настоящее время или будет в будущем? используйте <strong>сильный</strong> (сохранить).</p> </li> </ul><p>чтобы проиллюстрировать: хочу ли я удерживать <strong>имя</strong> "лиза миллер" (<em> копию </em>) или я хочу удержать на <strong>человек</strong> лиза миллер (<em> сильная </em>)? позже ее имя может измениться на "лиза смит", но она все равно останется тем же человеком.</p>

<p>с помощью этого примера можно объяснить, как скопировать и сохранить, например:</p><pre><code>nsmutablestring *somename = [nsmutablestring stringwithstring:@"chris"]; person *p = [[[person alloc] init] autorelease]; p.name = somename; [somename setstring:@"debajit"]; </code></pre><p>если свойство имеет тип copy, то</p><p>для строки <code>[person name]</code> будет создана новая копия, которая будет содержать содержимое строки <code>somename</code>. теперь любая операция со строкой <code>somename</code> не будет влиять на <code>[person name]</code>.</p><p>строки <code>[person name]</code> и <code>somename</code> будут иметь разные адреса памяти.</p><p>но в случае сохранения,</p><p>оба <code>[person name]</code> будут содержать тот же адрес памяти, что и строка соменам, просто число сохраненных строк соменам будет увеличено на 1.</p><p>таким образом, любое изменение в строке somename будет отражено в строке <code>[person name]</code>.</p>

<p>безусловно, добавление "copy" в объявление свойства противоречит использованию объектно-ориентированной среды, в которой объекты в куче передаются по ссылке - одно из преимуществ, которые вы получаете, заключается в том, что при изменении объекта все ссылки на этот объект увидеть последние изменения. многие языки предоставляют "ref" или подобные ключевые слова, чтобы позволить типам значений (то есть структурам в стеке) получить выгоду от того же поведения. лично я бы использовал копирование экономно, и если бы я чувствовал, что значение свойства должно быть защищено от изменений, внесенных в объект, которому он был назначен, я мог бы вызвать метод копирования этого объекта во время назначения, например:</p><pre><code>p.name = [somename copy]; </code></pre><p>конечно, при проектировании объекта, содержащего это свойство, только вы будете знать, выиграет ли проект от шаблона, в котором назначения получают копии - <a href="http://cocoawithlove.com/2010/06/assign-retain-copy-pitfalls-in-obj-c.html">cocoawithlove.com</a> может сказать следующее:</p><p>"вы должны использовать средство доступа к копии, когда параметр setter может быть изменяемым <em>, но вы не можете изменить внутреннее состояние свойства без предупреждения </em>" - так суждение о том, сможете ли вы вынести ценность неожиданного изменения, принадлежит вам самому. представьте себе этот сценарий:</p><pre><code>//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. </code></pre><p>в этом случае, без использования копирования, наш контактный объект автоматически принимает новое значение; если бы мы использовали его, нам бы пришлось вручную убедиться, что изменения были обнаружены и синхронизированы. в этом случае сохранение семантики может быть желательным; в другом случае копия может быть более подходящей.</p>

<pre><code>@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 </code></pre>

<p>вы должны использовать <strong> copy </strong> все время для объявления свойства nsstring</p><pre><code>@property (nonatomic, copy) nsstring* name; </code></pre><p>вы должны прочитать их для получения дополнительной информации о том, возвращает ли она неизменяемую строку (в случае, если изменяемая строка была передана) или возвращает сохраненную строку (в случае, если неизменная строка была передана)</p><p> <a href="https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/protocols/nscopying\_protocol/reference/reference.html">nscopying protocol reference</a> </p><blockquote> <p>реализуйте nscopying, сохраняя оригинал вместо создания новой копии, когда класс и его содержимое неизменны</p> </blockquote><p> <a href="http://www.objc.io/issue-7/value-objects.html">объекты-значения</a> </p><blockquote> <p>итак, для нашей неизменной версии мы можем просто сделать это:</p> </blockquote><pre><code>- (id)copywithzone:(nszone *)zone { return self; } </code></pre>