2012-05-12 2 views
4

Я не понимаю этого, если только это не потому, что я выпускаю собственность вместо ivar. Может ли кто-то пролить свет на проблему?Неправильный декремент ссылочного счета, который не принадлежит на данный момент

self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000]; 
    [self.dataToBeLoaded release]; 

Предупреждение: Incorrect decrement of the reference count of an object that is not owned by the caller.

Свойство dataToBeLoaded имеет атрибут сохранения, связанный с его установщиком.

Мое понимание - это инициализация alloc увеличивает счетчик удержания, а присвоение свойства увеличивает счетчик удержания. Поскольку я только один, чтобы сохранить его один раз, поэтому я освобождаю его сразу после назначения.

UPDATE - некоторые экспериментальные результаты:

Поскольку я отметил в своих комментариях ниже, что я получил противоречивые советы о том, что сохраняют свойство делает синтезированную сеттер, я думал, что я хотел бы сделать небольшой эксперимент с использованием приведенный выше код, модифицированный с некоторым протоколированием:

NSLog(@"retain 1 = %d", [dataToBeLoaded_ retainCount]); 
self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000]; 
NSLog(@"retain 2 = %d", [dataToBeLoaded_ retainCount]); 
[self.dataToBeLoaded release]; 
NSLog(@"retain 3 = %d", [dataToBeLoaded_ retainCount]); 

результатов в каждом журнале заявлении было 0, 2 и 1.

Видимо, это не представляется возможным вмешаться на адрес или код инициализации, чтобы увидеть, что счет сохранения идет от 0 до 1 до 2. Я мог бы подклассифицировать класс NSMutableData, но был коротким во времени.

Я знаю, что много сказано, что вы не можете полагаться на значение свойства retainCount, но то, что у меня кажется последовательным, и я ожидал бы разумного поведения в короткой области кода, как показано в примере. Поэтому я склонен полагать, что предыдущие советы верны - свойство сохранения - это обещание включить сохранение в сеттер. Итак, здесь у меня есть сохранение из alloc/init и сохранение из вызова setter. Таким образом, сохранить счетчик устанавливается равным 2.

Когда я запускаю этот код:

NSMutableData *theData; 
NSLog(@"retain 1 = %d", [theData retainCount]); 
theData= [[NSMutableData alloc] initWithLength:10000]; 
NSLog(@"retain 1a = %d", [theData retainCount]); 
self.dataToBeLoaded = theData; 
NSLog(@"retain 2 = %d", [theData retainCount]); 
[self.dataToBeLoaded release]; 
NSLog(@"retain 3 = %d", [theData retainCount]); 

Сохранять количество в каждом журнале утверждения 0, 1, 2, 1.

Так что я доказательства, что предполагает, что сеттер предоставляет retain. Это кажется скорее обещанием, чем подсказкой, потому что на самом деле это происходит.

Я открыт для других объяснений. Я не хочу быть высокомерным об этом. Я просто хочу понять, что происходит. Похоже, что предупреждение (в теме этого вопроса) действительно ложно и не о чем беспокоиться.

Еще один эксперимент выполняется с использованием assign, а не retain как атрибут в заявлении @property. С тем же кодом:

NSMutableData *theData; 
NSLog(@"retain 1 = %d", [theData retainCount]); 
theData= [[NSMutableData alloc] initWithLength:10000]; 
NSLog(@"retain 1a = %d", [theData retainCount]); 
self.dataToBeLoaded = theData; 
NSLog(@"retain 2 = %d", [theData retainCount]); 
[self.dataToBeLoaded release]; 
NSLog(@"retain 3 = %d", [theData retainCount]); 

сохранить счетчик на каждый срубе 0, 1, 1 (сеттер не сохранил), то сообщение об ошибке: message sent to deallocated instance. В последнем выпуске установлено, что счетчик удержания равен нулю, что вызвало освобождение.не

UPDATE 2

Окончательное обновление - когда синтезированный сеттер перекрывается с собственным кодом, то сохранить атрибут больше не наблюдается, если ваш сеттер явно не включает его. По-видимому (и это противоречит тому, что мне рассказывали в других разделах здесь), вы должны включить свое собственное сохранение в сеттер, если это то, что вы хотите. Хотя я не тестировал его здесь, вам, вероятно, нужно сначала выпустить старый экземпляр, иначе он будет просочиться. не

Этот обычай сеттер больше не имеет свойств атрибутов декларации @propety:

- (void) setDataToBeLoaded:(NSMutableData *)dataToBeLoaded { 
    dataToBeLoaded_ = dataToBeLoaded; 
} 

Это имеет смысл. Переопределите синтезированный сеттер и переопределите все объявленные свойства. Используйте синтезированный сеттер, и объявленные свойства наблюдаются в синтезированной реализации.

Атрибуты @property представляют собой «обещание» относительно того, как реализован синтезированный сеттер. Когда вы пишете пользовательский сеттер, вы по своему усмотрению.

ответ

0

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

1

Это просто означает, что вы выпускаете объект, который у вас нет.

Я бы назвал его с помощью экземпляра var напрямую, вместо использования getter, но не уверен, что это исправит ваши аналитические предупреждения. Также почему бы не использовать [NSMutableData dataWithLength: 1000]; который autoreleased и, следовательно, отпадает необходимость этого дополнительного вызова выпуска (! и, вероятно, избавиться от этого предупреждения тоже)

других способов, которыми Вы могли бы это исправить:

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000]; 
self.databToBeLoaded = data; 
[data release]; 
2

Моего предположение было бы, что метод

- (NSMutableData *)dataToBeLoaded; 

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

Либо использовать

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000]; 
self.dataToBeLoaded = data; 
[data release]; data = nil; 

или если вы можете, почему не ленитесь нагрузки это, когда вы на самом деле нужно?

- (NSMutableData *)dataToBeLoaded; 
{ 
    if (!_dataToBeLoaded) { 
     _dataToBeLoaded = [[NSMutableData alloc] initWithLength:1000]; 
    } 
    return _dataToBeLoaded; 
} 
4

Ключом к пониманию того, что делает нижеследующий код. Я напишу его в полном объеме для ясности:

[self setDataToBeLoaded:[[NSMutableData alloc] initWithLength:10000]]; 

Это создает объект с +1 сохранить счетчик и передает его в setDataToBeLoaded:. (*) Затем он отбрасывает свою ссылку на этот объект, протекая его.

[[self dataToBeLoaded] release]; 

Это вызывает dataToBeLoaded и освобождает объект возвращается. Нет никакого обещания, что объект, возвращаемый dataToBeLoaded, совпадает с объектом, переданным setDataToBeLoaded:. Вероятно, вы думаете, что они одинаковы, и, глядя на ваш код, вы, вероятно, можете убедить себя, что он всегда будет работать именно так, но это не обещание API.

код отправленный Antwan правильно:

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000]; 
self.dataToBeLoaded = data; 
[data release]; 

Это создает объект с +1 сохранить счетчик. Затем передает его методу, а затем отпускает его.

Или, если вы готовы использовать autorelease бассейн, вы можете упростить его:

self.dataToBeLoaded = [NSMutableData dataWithLength:1000]; 

(*) Технически это проходит сообщение для self, которые могут или не могут вызвать это метод, который будет вызван, но это замалчивает проблему. Для большинства целей притворяйтесь, что это вызов метода. Но do не делают вид, что он просто устанавливает свойство. Это действительно вызовет какой-то метод.


EDIT:

Может быть, этот код будет сделать этот вопрос немного яснее. Это указывает на общие решения кэширования:

.h 
@interface MYObject : NSObject 
@property (nonatomic, readwrite, strong) NSString *stuff; 
@end 

.m 
@interface MYObject() 
@property (nonatomic, readwrite, weak) MYStuffManager *manager; 

@implementation MYObject 

... Initialize manager ... 

- (NSString*)stuff { 
    return [self.manager stuffForObject:self]; 
} 

- (void)setStuff:(NSString *)stuff { 
    [self.manager setStuff:stuff forObject:self]; 
} 

manager Теперь, может быть, делает некоторые Foolery в фоновом режиме. Возможно, он кэширует различные копии stuff. Возможно, он их копирует. Может быть, он переносит их на другие объекты. Важно то, что вы не можете положиться на -stuff, всегда возвращающий тот же объект, который вы передали -setStuff:. Поэтому вы, конечно, не должны его отпускать.

Обратите внимание, что ничто в заголовке не указывает на это, и ничего не должно. Дело не в звонках. Но если вызывающий абонент выпустит результат -stuff, вы получите жесткие сбои.

@synthesize - это всего лишь стенография для написания некоторого утомительного кода (код, который реализует stuff и setStuff: как чтение и письмо ivar). Но ничто не говорит о том, что вы должны использовать @synthesize для своих объектов.

+0

Я понимаю, что сеттер является вызовом метода. Возможно, я до сих пор не совсем понимаю, что сеттер в том случае, когда свойство содержит атрибут сохранения. Я думал, что установщик для сохраненного свойства сначала освобождает ранее упомянутый экземпляр (или, по вашим словам, отбрасывает его), затем назначает переданную ссылку экземпляра, а затем сохраняет его. Теперь переданный экземпляр уже был сохранен один раз alloc/init. - Кстати, отброшенный ссылочный экземпляр не просочился в этот процесс, если его счетчик ссылок обращается в нуль. Я что-то пропустил в вашем объяснении? – Jim

+0

Вы предполагаете слишком много о своей конкретной реализации, а не обещаниях API. Вы предполагаете, что 'setDataToBeLoaded:' назначает ivar и сохраняет его. Это не обещано вашим API. Атрибут «сохранить» не является обещанием в API. Это подсказка, и она используется @synthesize (но @synthesize не имеет ничего общего с API). Надеюсь, что вы будете еще яснее: никогда не выпускайте то, что вы не приняли на себя. Вызов 'dataToBeLoaded' не получает права собственности на возвращаемое значение. У метода нет имени/copy/alloc в имени. Вы не владеете им. –

+0

Вы упоминаете, что вы не просачиваетесь. Вы на самом деле. Вы просачиваетесь, и вы перевыпускаете, и два из них балансируют, поэтому вы не видите утечки в «Инструменты».Но статический анализатор должен отметить оба. –

Смежные вопросы

 Смежные вопросы