Как скопировать объект в Objective-C

112

Мне нужно глубоко скопировать настраиваемый объект, у которого есть собственные объекты. Я читал и немного смущен тем, как наследовать NSCopying и как использовать NSCopyObject.

Бен
источник
1
Отличный Учебник по пониманию copy, mutableCopy и copyWithZone
horkavlna

Ответы:

192

Как всегда со ссылочными типами, есть два понятия «копия». Я уверен, что вы их знаете, но для полноты картины.

  1. Побитовая копия. Здесь мы просто копируем бит за битом - это то, что делает NSCopyObject. Почти всегда это не то, что вам нужно. Объекты имеют внутреннее состояние, другие объекты и т. Д. И часто предполагают, что только они содержат ссылки на эти данные. Побитовые копии нарушают это предположение.
  2. Глубокая, логичная копия. В этом случае мы делаем копию объекта, но на самом деле не делаем это по крупицам - нам нужен объект, который ведет себя одинаково для всех намерений и целей, но не (обязательно) идентичный по памяти клон оригинала - Руководство по Objective C называет такой объект «функционально независимым» от его оригинала. Поскольку механизмы создания этих «интеллектуальных» копий различаются от класса к классу, мы просим сами объекты выполнить их. Это протокол NSCopying.

Вы хотите последнего. Если это один из ваших собственных объектов, вам нужно просто принять протокол NSCopying и реализовать зону (id) copyWithZone: (NSZone *). Вы можете делать все, что хотите; хотя идея состоит в том, чтобы сделать настоящую копию себя и вернуть ее. Вы вызываете copyWithZone во всех своих полях, чтобы сделать глубокую копию. Простой пример:

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  // We'll ignore the zone for now
  YourClass *another = [[YourClass alloc] init];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Адам Райт
источник
Но вы возлагаете на получателя скопированного объекта ответственность за его освобождение! Разве ты autoreleaseне должен, или я что-то здесь упускаю?
bobobobo,
30
@bobobobo: Нет, фундаментальное правило управления памятью в Objective-C: вы становитесь владельцем объекта, если вы создаете его с помощью метода, имя которого начинается с «alloc» или «new» или содержит «copy». copyWithZone:соответствует этим критериям, поэтому он должен возвращать объект со счетчиком удержания +1.
Стив Мэдсен,
1
@Adam Есть ли причина использовать allocвместо, allocWithZone:так как зона была передана?
Ричард
3
Что ж, зоны фактически не используются в современных средах выполнения на базе OS X (то есть я думаю, что они буквально никогда не используются). Но да, вы могли бы позвонить allocWithZone.
Адам Райт,
25

В документации Apple говорится

Версия подкласса метода copyWithZone: должна сначала отправить сообщение в super, чтобы включить его реализацию, если только подкласс не наследуется непосредственно от NSObject.

добавить к существующему ответу

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  YourClass *another = [super copyWithZone:zone];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Сакиб Сауд
источник
2
Поскольку YourClass происходит непосредственно от NSObject, я не думаю, что здесь это необходимо
Майк,
2
Хороший момент, но это общее правило, если это длинная иерархия классов.
Сакиб Сауд
8
Я получил сообщение об ошибке: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Я предполагаю, что это требуется только тогда, когда мы наследуем от какого-то другого настраиваемого класса, который реализуетcopyWithZone
Сэм
1
another.obj = [[obj copyWithZone: zone] автозапуск]; для всех подклассов NSObject. А для примитивных типов данных вы просто назначаете их -> another.someBOOL = self.someBOOL;
hariszaman
@Sam "NSObject сам по себе не поддерживает протокол NSCopying. Подклассы должны поддерживать протокол и реализовывать метод copyWithZone :. Подклассовая версия метода copyWithZone: должна сначала отправлять сообщение в super, чтобы включить его реализацию, если только подкласс не спускается напрямую из NSObject ". developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

Я не знаю разницы между этим кодом и моим, но у меня есть проблемы с этим решением, поэтому я прочитал немного больше и обнаружил, что мы должны установить объект, прежде чем возвращать его. Я имею в виду что-то вроде:

#import <Foundation/Foundation.h>

@interface YourObject : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *line;
@property (strong, nonatomic) NSMutableString *tags;
@property (strong, nonatomic) NSString *htmlSource;
@property (strong, nonatomic) NSMutableString *obj;

-(id) copyWithZone: (NSZone *) zone;

@end


@implementation YourObject


-(id) copyWithZone: (NSZone *) zone
{
    YourObject *copy = [[YourObject allocWithZone: zone] init];

    [copy setNombre: self.name];
    [copy setLinea: self.line];
    [copy setTags: self.tags];
    [copy setHtmlSource: self.htmlSource];

    return copy;
}

Я добавил этот ответ, потому что у меня много проблем с этой проблемой, и я понятия не имею, почему это происходит. Я не знаю разницы, но у меня это работает и, возможно, может быть полезно и для других :)

Фелипе Кирос
источник
3
another.obj = [obj copyWithZone: zone];

Я думаю, что эта строка вызывает утечку памяти, потому что вы objполучаете доступ к свойству through, которое (я полагаю) объявлено как retain. Таким образом, счетчик удержания будет увеличиваться за счет собственности и copyWithZone.

Я считаю, что это должно быть:

another.obj = [[obj copyWithZone: zone] autorelease];

или:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
источник
Нет, методы alloc, copy, mutableCopy, new должны возвращать объекты, не выпускаемые автоматически.
kovpas
@kovpas, ты уверен, что правильно меня понимаешь? Я не говорю о возвращаемом объекте, я говорю о его полях данных.
Szuwar_Jr
да, мой плохой, извините. не могли бы вы как-нибудь отредактировать свой ответ, чтобы я мог убрать минус? :))
ковпас
0

Также для копирования используется оператор ->. Например:

-(id)copyWithZone:(NSZone*)zone
{
    MYClass* copy = [MYClass new];
    copy->_property1 = self->_property1;
    ...
    copy->_propertyN = self->_propertyN;
    return copy;
}

Причина здесь в том, что полученный скопированный объект должен отражать состояние исходного объекта. Знак "." Оператор может вызвать побочные эффекты, поскольку он вызывает геттеры, которые, в свою очередь, могут содержать логику.

Алекс Ноласко
источник