Właściwość NSString: skopiować czy zachować?

331

Powiedzmy, że mam klasę o nazwie SomeClasso nazwie stringwłaściwości:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Rozumiem, że nazwa może być przypisana, NSMutableStringw którym to przypadku może to prowadzić do błędnego zachowania.

  • Czy w przypadku ciągów zawsze dobrym pomysłem jest użycie copyatrybutu zamiast retain?
  • Czy właściwość „skopiowana” jest w jakikolwiek sposób mniej wydajna niż właściwość „zachowana”?
PlagueHammer
źródło
6
Dalsze pytanie: czy powinno namezostać wydane deallocczy nie?
Chetan
7
@chetan Tak, powinno!
Jon

Odpowiedzi:

440

W przypadku atrybutów, których typem jest niezmienna klasa wartości zgodna z NSCopyingprotokołem, prawie zawsze należy określić copyw @propertydeklaracji. Określanie retainjest czymś, czego prawie nigdy nie chcesz w takiej sytuacji.

Oto dlaczego chcesz to zrobić:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

Bieżąca wartość Person.namewłaściwości będzie różna w zależności od tego, czy właściwość jest zadeklarowana, retainczy copy- będzie, @"Debajit"jeśli właściwość zostanie oznaczona retain, ale @"Chris"jeśli właściwość zostanie oznaczona copy.

Ponieważ w prawie wszystkich przypadkach chcesz zapobiec mutowaniu atrybutów obiektu za jego plecami, powinieneś zaznaczyć właściwości je reprezentujące copy. (A jeśli sam piszesz seter zamiast go używać @synthesize, powinieneś pamiętać, aby go używać copyzamiast retainniego.)

Chris Hanson
źródło
61
Ta odpowiedź mogła spowodować pewne zamieszanie (patrz robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Masz całkowitą rację co do NSString, ale uważam, że powiedziałeś o tym nieco zbyt ogólnie. Powodem, dla którego NSString powinien zostać skopiowany, jest to, że ma wspólną zmienną podklasę (NSMutableString). W przypadku klas, które nie mają zmiennej podklasy (szczególnie klasy, które sam piszesz), zwykle lepiej jest je zachować niż kopiować, aby uniknąć marnowania czasu i pamięci.
Rob Napier
63
Twoje rozumowanie jest nieprawidłowe. Nie powinieneś decydować, czy kopiować, czy zachować na podstawie czasu / pamięci, powinieneś ustalać to na podstawie pożądanej semantyki. Właśnie dlatego w odpowiedzi użyłem terminu „niezmienna klasa wartości”. Nie jest również kwestią tego, czy klasa ma zmienne podklasy, czy też sama jest zmienna.
Chris Hanson
10
Szkoda, że ​​Obj-C nie może wymusić niezmienności według typu. Jest to to samo, co brak stałej przechodniej C ++. Osobiście pracuję tak, jakby ciągi zawsze były niezmienne. Jeśli kiedykolwiek będę musiał użyć zmiennego ciągu, nigdy nie podam niezmiennego odwołania, jeśli będę mógł je później zmutować. Za coś innego uważałbym zapach kodu. W rezultacie - w moim kodzie (nad którym pracuję sam) używam retain na wszystkich moich ciągach. Gdybym pracował jako część zespołu, mógłbym patrzeć na to inaczej.
philsquared
5
@Phil Nash: Uważam, że używanie różnych stylów w projektach, nad którymi pracujesz sam, i projektach, które udostępniasz innym, to zapach. W każdym języku / frameworku obowiązują wspólne reguły lub style, na które zgadzają się programiści. Pomijanie ich w prywatnych projektach wydaje się błędne. A dla twojego uzasadnienia „W moim kodzie nie zwracam ciągów zmiennych”: To może działać dla twoich własnych ciągów, ale nigdy nie wiesz o ciągach, które otrzymujesz z frameworków.
Nikolai Ruhe
7
@Nikolai Po prostu nie używam NSMutableString, z wyjątkiem przejściowego typu „ kreatora ciągów” (z którego natychmiast biorę niezmienną kopię). Wolałbym, aby były to typy dyskretne - ale pozwolę, że fakt, że kopia może zachować, jeśli oryginalny ciąg nie jest modyfikowalny, łagodzi większość moich obaw.
philsquared
120

Kopia powinna być używana do NSString. Jeśli jest to Zmienne, to zostanie skopiowane. Jeśli nie, to zostaje po prostu zachowane. Dokładnie semantyka, którą chcesz w aplikacji (pozwól typowi robić to, co najlepsze).

Frank Krueger
źródło
1
Nadal wolałbym, aby zmienne i niezmienne formy były dyskretne, ale nie zdawałem sobie sprawy, że ta kopia może zostać zachowana, jeśli oryginalny ciąg jest niezmienny - co jest w większości przypadków tam. Dzięki.
philsquared
25
+1 za wzmiankę o tej NSStringwłasności zadeklarowanej jako copyi retaintak dostanie (jeśli oczywiście jest niezmienna). Innym przykładem, o którym mogę myśleć, jest NSNumber.
matm
Jaka jest różnica między tą odpowiedzią a tą, na którą głosował @GBY?
Gary Lyn
67

Czy w przypadku ciągów zawsze dobrym pomysłem jest użycie atrybutu kopiowania zamiast zachowania?

Tak - generalnie zawsze używaj atrybutu kopiowania.

Wynika to z faktu, że do Twojej właściwości NSString można przekazać instancję NSString lub instancję NSMutableString , a zatem nie możemy tak naprawdę ustalić, czy przekazywana wartość jest obiektem niezmiennym czy zmiennym.

Czy właściwość „skopiowana” jest w jakikolwiek sposób mniej wydajna niż właściwość „zachowana”?

  • Jeśli właściwość jest przekazywana do instancji NSString , odpowiedź brzmi „ Nie ” - kopiowanie nie jest mniej wydajne niż zachowanie.
    (To nie jest mniej wydajne, ponieważ NSString jest wystarczająco inteligentny, aby w rzeczywistości nie wykonywać kopii.)

  • Jeśli właściwość zostanie przekazana do instancji NSMutableString, odpowiedź brzmi „ Tak ” - kopiowanie jest mniej wydajne niż zachowanie.
    (Jest to mniej wydajne, ponieważ musi nastąpić faktyczny przydział pamięci i kopiowanie, ale jest to prawdopodobnie pożądana rzecz).

  • Mówiąc ogólnie, „skopiowana” właściwość może być mniej wydajna - jednak dzięki zastosowaniu NSCopyingprotokołu możliwe jest zaimplementowanie klasy, która jest „równie wydajna” do kopiowania, jak do zachowania. Instancje NSString są tego przykładem.

Ogólnie (nie tylko dla NSString), kiedy powinienem używać „kopiuj” zamiast „zachowaj”?

Należy zawsze używać, copygdy nie chcesz, aby stan wewnętrzny właściwości zmieniał się bez ostrzeżenia. Nawet w przypadku niezmiennych obiektów - poprawnie zapisane niezmienne obiekty będą skutecznie radziły sobie z kopiowaniem (zob. Następna sekcja dotycząca niezmienności i NSCopying).

retainObiekty mogą mieć przyczyny związane z wydajnością , ale wiąże się to z narzutem związanym z konserwacją - musisz zarządzać możliwością zmiany stanu wewnętrznego poza kodem. Jak mówią - optymalizuj ostatni.

Ale napisałem moją klasę, aby była niezmienna - czy nie mogę tego po prostu „zachować”?

Nie - użyj copy. Jeśli twoja klasa jest naprawdę niezmienna, najlepiej jest zaimplementować NSCopyingprotokół, aby twoja klasa powróciła sama, gdy copyjest używana. Jeśli to zrobisz:

  • Inni użytkownicy z Twojej klasy zyskają korzyści z wydajności, gdy z nich skorzystają copy.
  • copyAdnotacja sprawia własny kod bardziej linkujących - w copyadnotacja wskazuje, że naprawdę nie trzeba się martwić o stan tego obiektu zmieniającym się gdzie indziej.
TJez
źródło
39

Staram się przestrzegać tej prostej zasady:

  • Czy chcę zachować wartość obiektu w momencie, w którym przypisuję go do mojej właściwości? Użyj kopii .

  • Czy chcę trzymać obiekt i nie obchodzi mnie, jakie są jego wewnętrzne wartości w przyszłości? Użyj silnego (zachowaj).

Dla zilustrowania: Czy chcę zachować imię „Lisa Miller” ( kopia ), czy też chcę zachować osobę Lisa Miller ( silna )? Jej imię może później zmienić się na „Lisa Smith”, ale nadal będzie tą samą osobą.

Johannes Fahrenkrug
źródło
14

W tym przykładzie można skopiować i zachować kopię w następujący sposób:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

jeśli właściwość jest typu kopiuj, to

zostanie utworzona nowa kopia dla [Person name]ciągu, który będzie zawierał treść someNameciągu. Teraz każda operacja na someNamełańcuchu nie będzie miała wpływu na [Person name].

[Person name]a someNamełańcuchy będą miały różne adresy pamięci.

Ale w przypadku zatrzymania

oba [Person name]będą przechowywać ten sam adres pamięci co ciąg somename, tylko liczba zatrzymań ciągu somename zostanie zwiększona o 1.

Tak więc każda zmiana w łańcuchu nazwy będzie odzwierciedlona w [Person name]łańcuchu.

Divya Arora
źródło
3

Z pewnością umieszczenie „kopiowania” w deklaracji właściwości jest bardzo trudne w obliczu używania środowiska obiektowego, w którym obiekty na stercie są przekazywane przez referencję - jedną z korzyści, jakie tu otrzymujesz, jest to, że przy zmianie obiektu wszystkie odwołania do tego obiektu zobacz najnowsze zmiany. Wiele języków podaje „ref” lub podobne słowa kluczowe, aby umożliwić typom wartości (tj. Strukturom na stosie) czerpanie korzyści z tego samego zachowania. Osobiście korzystałbym z kopii oszczędnie, a jeśli czułem, że wartość właściwości powinna być chroniona przed zmianami dokonanymi w obiekcie, do którego została przypisana, mógłbym wywołać metodę kopiowania tego obiektu podczas przypisania, np .:

p.name = [someName copy];

Oczywiście, projektując obiekt zawierający tę właściwość, tylko Ty będziesz wiedział, czy projekt korzysta ze wzorca, w którym zadania są kopiowane - Cocoawithlove.com ma następujące zdanie:

„Powinieneś użyć akcesorium kopiującego, gdy parametr ustawiający może być zmienny, ale nie możesz zmienić stanu wewnętrznego właściwości bez ostrzeżenia ” - więc ocena, czy możesz znieść zmianę wartości, należy do ciebie. Wyobraź sobie ten scenariusz:

//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.

W takim przypadku, bez użycia kopii, nasz obiekt kontaktu automatycznie przyjmuje nową wartość; gdybyśmy go jednak użyli, musielibyśmy ręcznie upewnić się, że zmiany zostały wykryte i zsynchronizowane. W takim przypadku zachowanie semantyki może być pożądane; w innym egzemplarz może być bardziej odpowiedni.

Clarkeye
źródło
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
Len
źródło
0

Powinieneś używać kopii cały czas, aby zadeklarować właściwość NSString

@property (nonatomic, copy) NSString* name;

Należy przeczytać je, aby uzyskać więcej informacji na temat tego, czy zwraca łańcuch niezmienny (w przypadku przekazania łańcucha zmiennego), czy zwraca zachowany ciąg (w przypadku przekazania łańcucha niezmiennego)

Odwołanie do protokołu NSCopying

Wdrożenie NSCopying poprzez zachowanie oryginału zamiast tworzenia nowej kopii, gdy klasa i jej zawartość są niezmienne

Obiekty wartości

Tak więc dla naszej niezmiennej wersji możemy po prostu to zrobić:

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}
onmyway133
źródło
-1

Ponieważ nazwa jest (niezmienna) NSString, kopiowanie lub zachowanie nie ma znaczenia, jeśli ustawisz inną NSStringna nazwę. Innymi słowy, kopia zachowuje się tak, jak zachowaj, zwiększając liczbę referencji o jeden. Myślę, że jest to automatyczna optymalizacja klas niezmiennych, ponieważ są one niezmienne i nie trzeba ich klonować. Ale gdy NSMutalbeString mstrzostanie ustawiona nazwa a, treść mstrzostanie skopiowana ze względu na poprawność.

GBY
źródło
1
Mylisz zadeklarowany typ z rzeczywistym typem. Jeśli użyjesz właściwości „zachowaj” i przypiszesz NSMutableString, ten NSMutableString zostanie zachowany, ale nadal będzie można go modyfikować. Jeśli użyjesz „kopiuj”, niezmienna kopia zostanie utworzona po przypisaniu NSMutableString; odtąd „kopia” właściwości będzie po prostu zachowywać, ponieważ sama kopia łańcucha zmiennego jest niezmienna.
gnasher729
1
Brakuje tutaj kilku ważnych faktów, jeśli użyjesz obiektu pochodzącego z zachowanej zmiennej, kiedy zmienna ta zostanie zmodyfikowana, podobnie jak twój obiekt, jeśli pochodzi z skopiowanej zmiennej, twój obiekt będzie miał bieżącą wartość zmiennej, która nie zmieni się
Bryan P
-1

Jeśli ciąg jest bardzo duży, kopiowanie wpłynie na wydajność, a dwie kopie dużego ciągu zajmą więcej pamięci.

Jacek
źródło