Objective-C указатели? - PullRequest
       16

Objective-C указатели?

12 голосов
/ 09 февраля 2009

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

ClassOne *pointer = [[ClassOne alloc]init];

ClassTwo *foo = [[ClassTwo alloc]init], *foo2; 

 foo2 = [foo add: pointer];
 [foo release]; 
 foo = foo2

[pointer release];

[foo release];

Ответы [ 7 ]

22 голосов
/ 09 февраля 2009

В Objective-C Cocoa мы работаем с полуавтоматическим управлением памятью с подсчетом ссылок. При выделении памяти для объекта, сохранении объекта или вызове метода copy на объекте счетчик сохранения (счетчик ссылок) увеличивается на 1. При вызове release для объекта он уменьшает счетчик хранения на единицу. При вызове autorelease для объекта, release будет вызываться для объекта в некоторый момент в будущем (во время основного цикла выполнения, когда не выполняется ни один из вашего собственного кода, поэтому он не будет извлекать ссылку из под тобой как ты пытаешься его использовать). Когда счет сохранения достигает 0, объект может быть освобожден.

В общем, если вы звоните retain на объект, вы сигнализируете о своем интересе к нему, и вы несете ответственность за совершение вызова release или autorelease в какой-то момент, когда вы не больше не интересует объект. Аналогично, если вы вызываете alloc или copy метод для объекта, вы сигнализируете о своей заинтересованности в объекте и должны сопоставить его с release или autorelease где-то вниз по линии.

Эта ссылка в значительной степени охватывает рекомендации, которые Apple использует (и вы должны использовать) для управления памятью: Простые правила для управления памятью в Какао

Давайте пройдемся по коду построчно:

ClassOne *pointer = [[ClassOne alloc]init];

pointer указывает на вновь выделенный объект ClassOne с счетом сохранения 1, так как мы вызвали на нем alloc. Мы обязаны позвонить release или autorelease на pointer в какой-то момент в будущем.

ClassTwo *foo = [[ClassTwo alloc]init], *foo2;

foo указывает на вновь выделенный объект ClassTwo с счетом сохранения 1, так как мы вызвали на нем alloc. Мы обязаны позвонить release или autorelease на foo в какой-то момент в будущем.

foo2 сейчас не указывает ни на что конкретное. Это не безопасно использовать.

foo2 = [foo add: pointer];

pointer был добавлен к foo (что бы это ни значило; мы не знаем реализацию). foo мог бы вызвать retain на pointer, чтобы сообщить о своем интересе к нему, и добавить его в качестве поля, или он мог бы добавить pointer к коллекции (в этом случае ответственность за вызов * 1052 лежит на коллекции). * на него при добавлении объекта и release при удалении объекта). В любом случае, это не влияет на наш блок кода, поэтому нам все равно, что происходит под капотом

Ссылка, возвращаемая этим методом, может быть самой pointer, или это может быть автоматически выпущенная копия pointer; у нас нет доступа к API или реализации, чтобы сказать нам, какой.

В любом случае мы не обязаны звонить release по этому объекту. Если бы у метода было copy в имени или если бы мы вызвали retain в возвращенной ссылке (например, foo2 = [[foo add:pointer] retain];), то оставшееся количество было бы увеличено на 1, и мы должны были бы вызвать release или autorelease на нем.

[foo release];

Объект, на который ссылается foo, был освобожден, что означает, что его счетчик хранения был уменьшен на 1. Для этого примера, это соединение с вызовом alloc, которое мы сделали в строке 2, поэтому счетчик хранения уменьшится до 0 , что делает foo право на освобождение.

В целом, однако, нам все равно, был ли объект освобожден или нет; нам просто нужно убедиться, что мы объединяем любые вызовы alloc, copy или retain с одним и тем же числом вызовов release или autorelease. Если мы зарегистрируем интерес к объекту в любое время, мы обязаны освободить его, иначе у нас будут утечки памяти.

 foo = foo2;

foo теперь указывает на тот же объект, на который ссылается foo2. Помните, что мы не вызвали метод alloc или copy, когда получили foo2, и мы не зарегистрировали интерес к нему, вызвав retain. Поскольку мы не обязаны звонить release на foo2, мы не обязаны звонить release на foo.

[pointer release];
Количество сохраненных элементов

pointer было уменьшено на 1. Это могло привести к тому, что его количество сохраненных записей равно 0 или нет, это зависит от того, что foo сделал с ним, когда мы добавили его. Тем не менее, нам все равно; мы завершили нашу ответственность до pointer, позвонив по ней release, чтобы соответствовать вызову alloc, который мы сделали в начале. Хотя pointer может все еще быть после этого вызова, мы не можем сделать такое предположение, и попытка что-либо сделать с объектом, на который ранее ссылался указатель, была бы ошибкой (хотя мы могли бы изменить pointer, чтобы указывать на что-то другое свободно ).

[foo release];

Если автор этого кода следовал соглашениям Apple об управлении памятью, то в этом нет необходимости. Мы не обязаны звонить release на foo или foo2 (они указывают на один и тот же объект, помните). Это не приведет к поломке кода; вызывать что-либо по ссылке nil по сути дела нельзя. Однако это может привести к путанице для любого, кто просматривает код.

Теперь автор этого кода мог нарушить соглашения об управлении памятью. Он мог бы сделать так, чтобы add call возвратил копию pointer без вызова autorelease, и в этом случае вызывающий абонент несет ответственность за вызов release для него. Это очень плохая форма, и если вам нужно столкнуться с кодом, который нарушает соглашение об управлении памятью, укажите, где вы его используете, и как он нарушает соглашение, чтобы избежать путаницы в будущем.

3 голосов
/ 09 февраля 2009
ClassOne *pointer = [[ClassOne alloc]init];

Переменная pointer является указателем на объект класса ClassOne. Ему присвоено значение вновь созданного объекта.

ClassTwo *foo = [[ClassTwo alloc]init], *foo2;

*foo и *foo2 являются объектами класса ClassTwo. Только foo назначается вновь созданному объекту. foo2 может указывать на что-либо, поэтому его небезопасно использовать перед присвоением ему значения.

 foo2 = [foo add: pointer];

foo2 присваивается значение: я предполагаю, что сообщение add: класса ClassTwo создает объект (подпись метода должна быть -(ClassTwo*)add:(ClassOne*);)

 [foo release];

Объект, на который указывает foo, больше не нужен.

 foo = foo2;

Переменной foo присвоено значение foo2: оба указывают на один и тот же объект.

[pointer release];

Объект, на который указывает pointer, больше не нужен.

[foo release];

Объект, на который указывает foo (а также foo2), больше не нужен.

1 голос
/ 11 февраля 2009

Ух ты, спасибо всем за отличные отзывы!

Я предполагаю, что на самом деле я имею в виду ссылку на объект, а не указатель. Тем не менее, я предполагаю, что добавленный , *foo2 находится в той же памяти, что и foo. Также foo2 освобождается из памяти форм одновременно с foo. У меня все еще есть намного больше, чтобы наклониться, но один день за один раз!

1 голос
/ 09 февраля 2009

Это действительно зависит от того, что делает [foo add:pointer];. Похоже, что он возвращает копию из foo и сохраняет ее. Это явно плохой дизайн, поскольку из метода должно быть очевидно, что возвращаемый объект является копией / ссылкой. Методы с именем add: не должны возвращать копию.

Шаг за шагом:

// this somehow creates a retained copy of foo.
foo2 = [foo add:pointer];

 // foo is released and gets destroyed.
[foo release];

// makes foo point to the same object as foo2
// (`foo` has no connection to the former object anymore)
foo = foo2;

// foo (and foo2 as they point to the same object) are released
[foo release];
1 голос
/ 09 февраля 2009

Из вашего простого примера действительно трудно сказать, что происходит. Обычно методы типа [class add:] возвращают void, поэтому они должны выдавать предупреждение компилятора, что 'значение void не игнорируется, как должно быть.'

Так что без дополнительной информации немного сложно разобраться.

Несколько вещей, которые нужно иметь в виду:

  • вы можете отправлять команды 'nil' в objc. Итак, если [foo add: pointer] возвращает ноль, то вы можете вызывать 'release' весь день без каких-либо последствий.

  • retainCount - ваш друг. Вы можете вызвать его на любом объекте NSO, чтобы увидеть, сколько объектов удерживает его. Это также может помочь вам отследить проблему.

  • Наконец, включена ли сборка мусора?

1 голос
/ 09 февраля 2009

Потому что вы не выпустили его. Вы выпускаете ссылки здесь, а не освобождаете указатели. Это не совсем то же самое. Когда вы делаете [foo release] во второй раз, вы освобождаете ссылку на foo, созданную при назначении foo2 для foo.

Чтобы освободить ссылку на foo2, вам нужно вызвать функцию release для этой ссылки, а не ее копию.

0 голосов
/ 09 февраля 2009

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

Вот ссылка на указатели в википедии .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...