Владение объектом в stringWithString и initWithString в NSString - PullRequest
9 голосов
/ 25 ноября 2008

Я понимаю, что любой метод init ... инициализирует новый объект и что NSString stringWithString создает копию строки параметра как новый объект. Я также понимаю, что, будучи владельцем объектов, я могу контролировать освобождение / освобождение любых объектов, которые я выделяю. Что я не понимаю, так это когда я буду использовать метод stringWithString, поскольку любая локальная переменная, назначенная таким образом, будет иметь свою «память», принадлежащую NSString, вместо локального класса.

Книга "Программирование в Objective C" Кочана (1-е изд) использует следующий код (см. Стр. 342-344), чтобы объяснить, что initWithString предпочтительнее stringWithString, поскольку класс AddressCard будет владеть содержимым переменной name. Также я не получаю никаких ошибок при повторных вызовах версии setName с помощью метода stringWithString. ТИА !!

//header file has appropriate declarations but not included here:
#import "AddressCard.h"

@implementation AddressCard;

-(NSString *) name
{
   return name;
}

//Recommended code:
-(void) setName: (NSString *) theName
{
   [name release]
   name = [[NSString alloc] initWthString: theName];
}

//Incorrect code according to Kochan:
-(void) setName: (NSString *) theName
{
   [name release]
   name = [NSString stringWthString: theName];
}

//rest of class implementation code snipped
@end

Ответы [ 5 ]

27 голосов
/ 25 ноября 2008

Чего я не понимаю, так это когда бы я использовал метод stringWithString, поскольку любая локальная переменная, назначенная таким образом, будет иметь свою «память», принадлежащую NSString, вместо локального класса.

Что? Нет.

Правила просты:

  • Любой объект, возвращаемый alloc, copy, copyWithZone или new, имеет счет сохранения 1.
  • retain увеличивает количество сохраняемых объектов принимающего объекта.
  • release уменьшает количество сохраняемых объектов принимающего объекта.
  • autorelease указывает текущему пулу автоматического выпуска отправлять принимающему объекту сообщение release «позже».
  • Любой фабричный метод, в имени которого нет «new» или «copy» (например, stringWithString:), возвращает объект, который был автоматически выпущен от вашего имени.

Или немного переварили:

  • Любой метод, имя которого содержит copy, alloc, retain или new, возвращает принадлежащий вам объект.
  • Любой метод, который этого не делает, возвращает объект, который вам не принадлежит.
  • Чтобы владеть объектом, сохраните его.

Неправильная реализация setName:, которую вы показываете, неверна, потому что она сохраняет автоматически выпущенный объект в переменной экземпляра, когда вы хотите владеть объектом. Вы должны сохранить его или, в этом случае, скопировать его. Один из способов - просто использовать alloc и initWithString:, как показано в правильном примере; другой путь будет copy.

Руководство по программированию управления памятью для Cocoa объясняет все. Каждый программист Cocoa или Cocoa Touch должен время от времени читать или перечитывать его.

6 голосов
/ 25 ноября 2008

На самом деле оба сеттера неверны. «Неправильный» неправильный по общим причинам управления памятью (которые хорошо изложены в другом месте). «Рекомендуемый» является неправильным по 2 причинам:

  1. если (имя == имя), то вы может освободить ваш объект в первая строка, а затем попытка использовать освобожденный объект как параметр -initWithString: на вторая строка, в результате чего не определено поведение.
  2. -initWithString: не обрабатывает передачу изящно ноль.

«Правильный» (ИМХО) метод:

-(void) setName: (NSString *) theName
{
   if (theName == name) return; // if they're equal, no need to do anything further
   [name release];
   name = [theName copy];  // sets name to nil if theName is nil
}

Для большинства объектов вы на самом деле захотите -core вместо -copy в третьей строке, но для строк почти всегда лучше скопировать.

3 голосов
/ 25 ноября 2008

Разница между initWithString и stringWithString в том, что stringWithString возвращает автоматически освобожденный указатель. Это означает, что вам не нужно специально его выпускать, поскольку об этом позаботятся в следующий раз, когда пул авто-релизов очистит все автоматически выпущенные указатели.

initWithString, с другой стороны, возвращает указатель с счетом сохранения 1 - вам нужно вызвать release для этого указателя, иначе это приведет к утечке памяти.

См. /96629/kakova-stoimost-ispolzovaniya-avto-reliza-v-kakao по ряду причин, по которым вам следует использовать автоматический выпуск по сравнению с выпуском.

0 голосов
/ 26 ноября 2008

Чего я не понимаю, так это когда бы я использовал метод stringWithString, поскольку любая локальная переменная, назначенная таким образом, будет иметь свою «память», принадлежащую NSString, вместо локального класса.

Строка, созданная с помощью stringWithString:, не принадлежит NSString, она принадлежит NSAutoreleasePool (хотя в нескольких местах может retain объект, что делает владение общим).

При stringWithString: строка станет недействительной при следующей обработке пула автоматического выпуска (обычно во время следующего цикла событий приложения), поскольку NSAutoreleasePool будет release указатель. Если вы не retain редактировали строку до этого, любой указатель на нее (name в случае вашего класса) будет недействительным (переменная name все еще будет существовать, но она будет указывать на мусор).

Авто-релиз хорош, если вы не намерены сохранять указатели на NSString, но, поскольку вы намерены сохранить указатель, вам нужно retain NSString. initWithString: автоматически дает вам счет удержания 1.

0 голосов
/ 25 ноября 2008

В приведенном выше Неверном коде при следующем обращении к имени после вызова setName вы получите ошибку исключения, поскольку объект будет освобожден. Вы можете использовать либо «правильный» код, либо заключить вызов stringWithString в явный вызов сохранения:

name = [[NSString stringWithString: theName] retain];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...