Як скопіювати об’єкт у Objective-C


112

Мені потрібно глибоко скопіювати нестандартний об’єкт, у якого є власні об’єкти. Я читав і трохи розгублений, як успадкувати NSCopying і як використовувати NSCopyObject.


1
Великий Підручник для розуміння копії, mutableCopy і copyWithZone
horkavlna

Відповіді:


192

Як завжди у посилальних типах, існує два поняття "копія". Я впевнений, що ви їх знаєте, але для повноти.

  1. Побітова копія. У цьому ми просто копіюємо біт пам'яті на біт - це те, що робить NSCopyObject. Майже завжди це не те, чого ти хочеш. Об'єкти мають внутрішній стан, інші об'єкти тощо, і часто роблять припущення, що вони єдині, на яких посилаються ці дані. Побітові копії порушують це припущення.
  2. Глибока логічна копія. У цьому ми робимо копію об'єкта, але насправді не робимо це побіжно - ми хочемо, щоб об’єкт поводився однаково за всіма намірами та цілями, але не є (обов’язково) клоном оригіналу, що відповідає пам’яті - посібник з об'єктивного 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: повинна надіслати повідомлення супер першим, щоб включити його реалізацію, якщо тільки підклас не походить безпосередньо з 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: зона] автовипуск]; для всіх підкласів NSObject. А для примітивних типів даних ви просто призначите їх -> else.someBOOL = self.someBOOL;
hariszaman

@Sam "NSObject сам не підтримує протокол NSCopying. Підкласи повинні підтримувати протокол та реалізовувати метод copyWithZone:. Версія підкласу методу copyWithZone: повинна надіслати повідомлення супер першим, щоб включити його реалізацію, якщо тільки підклас не спадає безпосередньо від 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через властивість, яка (я припускаю) оголошена як retain. Отже, кількість утримань збільшиться на майно та copyWithZone.

Я вважаю, що це повинно бути:

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

або:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 

Ні, методи алокації, копіювання, зміни файлу Копіювання, нові повинні повертати об'єкти, що не з'явилися автоматично
ковпас

@kovpas, ти впевнений, що ти мене правильно розумієш? Я не кажу про повернутий об'єкт, я кажу про це поля даних.
Szuwar_Jr

так, мій поганий, вибач. Чи можете ви, будь ласка, якось відредагувати свою відповідь, щоб я міг зняти мінус? :))
ковпас

0

Існує також використання оператора -> для копіювання. Наприклад:

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

Міркування тут в результаті скопійованого об'єкта повинно відображати стан вихідного об'єкта. "". Оператор може вводити побічні ефекти, оскільки цей викликає одержувачів, які в свою чергу можуть містити логіку.

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