Jak skopiować obiekt w Objective-C


112

Muszę głęboko skopiować niestandardowy obiekt, który ma własne obiekty. Czytałem i jestem trochę zdezorientowany, jak dziedziczyć NSCopying i jak używać NSCopyObject.


1
Świetny TUTORIAL dla zrozumienia copy, mutableCopy and copyWithZone
horkavlna

Odpowiedzi:


192

Jak zawsze w przypadku typów referencyjnych, istnieją dwa pojęcia „kopiowania”. Jestem pewien, że je znasz, ale dla kompletności.

  1. Kopia bitowa. W tym przypadku po prostu kopiujemy pamięć bit po bicie - to właśnie robi NSCopyObject. Prawie zawsze nie tego chcesz. Obiekty mają stan wewnętrzny, inne obiekty itp. I często zakładają, że są jedynymi obiektami zawierającymi odniesienia do tych danych. Kopie bitowe łamią to założenie.
  2. Głęboka, logiczna kopia. W tym celu tworzymy kopię obiektu, ale bez robienia tego krok po kroku - chcemy, aby obiekt zachowywał się tak samo we wszystkich zamiarach i celach, ale nie jest (koniecznie) identycznym z pamięcią klonem oryginału - podręcznik Celu C nazywa taki obiekt „funkcjonalnie niezależnym” od oryginału. Ponieważ mechanizmy tworzenia tych „inteligentnych” kopii różnią się w zależności od klasy, prosimy same obiekty o ich wykonanie. To jest protokół NSCopying.

Chcesz tego drugiego. Jeśli jest to jeden z twoich własnych obiektów, wystarczy zaadoptować protokół NSCopying i zaimplementować strefę - (id) copyWithZone: (NSZone *). Możesz robić, co chcesz; chociaż chodzi o to, żeby zrobić prawdziwą kopię siebie i zwrócić ją. Wywołujesz copyWithZone na wszystkich swoich polach, aby utworzyć dokładną kopię. Prosty przykład to

@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;
}

Ale sprawiasz, że odbiorca skopiowanego obiektu jest odpowiedzialny za jego uwolnienie! Nie powinieneś tego robić autorelease, czy czegoś mi brakuje?
bobobobo

30
@bobobobo: Nie, podstawowa zasada zarządzania pamięcią Objective-C brzmi: Przejmujesz prawo własności do obiektu, jeśli tworzysz go za pomocą metody, której nazwa zaczyna się od „przydziel” lub „nowy” lub zawiera słowo „kopia”. copyWithZone:spełnia te kryteria, dlatego musi zwrócić obiekt z liczbą zachowań +1.
Steve Madsen

1
@Adam Czy jest powód, aby używać alloczamiast od allocWithZone:czasu przekazania strefy?
Richard

3
Cóż, strefy są praktycznie nieużywane w nowoczesnych środowiskach wykonawczych opartych na systemie OS X (tj. Myślę, że dosłownie nigdy nie są używane). Ale tak, możesz zadzwonić allocWithZone.
Adam Wright,


25

Dokumentacja Apple mówi

Wersja metody copyWithZone: podklasy powinna najpierw wysłać wiadomość do super, aby uwzględnić jej implementację, chyba że podklasa pochodzi bezpośrednio z NSObject.

dodać do istniejącej odpowiedzi

@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
Ponieważ YourClass pochodzi bezpośrednio z NSObject, nie sądzę, aby to było konieczne tutaj
Mike

2
Słuszna uwaga, ale jest to ogólna zasada, na wypadek gdyby była to długa hierarchia klas.
Saqib Saud

8
Mam błąd: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Wydaje mi się, że jest to wymagane tylko wtedy, gdy dziedziczymy z innej klasy niestandardowej, która implementujecopyWithZone
Sam

1
another.obj = [[obj copyWithZone: zone] autorelease]; dla wszystkich podklas NSObject. A prymitywnym typom danych po prostu je przypisujesz -> another.someBOOL = self.someBOOL;
hariszaman

@Sam "NSObject sam nie obsługuje protokołu NSCopying. Podklasy muszą obsługiwać protokół i implementować metodę copyWithZone:. Wersja metody copyWithZone: podklasy powinna najpierw wysyłać wiadomość do super, aby uwzględnić jej implementację, chyba że podklasa schodzi bezpośrednio z NSObject. ” developer.apple.com/documentation/objectivec/nsobject/…
s4mt6

21

Nie znam różnicy między tym kodem a moim, ale mam problemy z tym rozwiązaniem, więc przeczytałem trochę więcej i stwierdziłem, że musimy ustawić obiekt przed jego zwróceniem. Mam na myśli coś takiego:

#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;
}

Dodałem tę odpowiedź, ponieważ mam wiele problemów z tym problemem i nie mam pojęcia, dlaczego tak się dzieje. Nie znam różnicy, ale działa na mnie i może przyda się też innym :)


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

Myślę, że ta linia powoduje wyciek pamięci, ponieważ dostęp do niej uzyskuje się objpoprzez właściwość, która jest (zakładam) zadeklarowana jako retain. Tak więc liczba zatrzymań zostanie zwiększona o własność i copyWithZone.

Uważam, że powinno to być:

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

lub:

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

Nie, metody przydzielanie, kopiowanie, mutableCopy, new powinny zwracać obiekty, które nie są udostępniane automatycznie.
kovpas

@kovpas, jesteś pewien, że mnie rozumiesz, prawda? Nie mówię o zwracanym obiekcie, mówię o jego polach danych.
Szuwar_Jr

tak, moja wina, przepraszam. czy mógłbyś jakoś zmienić swoją odpowiedź, abym mógł usunąć minus? :))
kovpas

0

Istnieje również użycie operatora -> do kopiowania. Na przykład:

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

Rozumowanie jest takie, że wynikowy skopiowany obiekt powinien odzwierciedlać stan oryginalnego obiektu. „.” Operator mógłby wprowadzić efekty uboczne, ponieważ wywołuje metody pobierające, które z kolei mogą zawierać logikę.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.