Добавление не NSObjects к NSMutableArray - PullRequest
13 голосов
/ 26 октября 2011

Это недавнее обсуждение SO сбило меня с толку.Прототип NSMutableArray для addObject: равен

- (void)addObject:(id)anObject

и id определяется в objc.h как

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id; 

Когда я добавляю NSObject или подкласс к NSMutableArrayего счетчик хранения увеличивается, а когда я удаляю его из NSMutableArray, он уменьшается.Означает ли это, что если id type, который не является NSObject или подклассом, добавлен к NSMutableArray, он должен ответить, чтобы сохранить и освободить сообщения?Определение id, похоже, не заставляет этого.Является ли объективной директивой C то, что любой id type должен отвечать на стандартные сообщения управления памятью?

Ответы [ 6 ]

7 голосов
/ 26 октября 2011

Суровая правда о большинстве контейнеров Foundation (и, в некоторой степени, о большинстве классов, разработанных Apple, а также о большинстве классов, разработанных третьими лицами) заключается в том, что когда метод принимает тип id, он должен действительно читать id<NSObject> , что означает любой тип, который отвечает на протокол NSObject. Экземпляры классов, которые не являются частью иерархии NSObject, вряд ли будут реагировать на -retain и -release, что особенно неудобно при попытке добавить их в контейнер. Они также вряд ли ответят на -hash, -isEqual:, -description, -copy и на все другие методы, которые контейнеры Foundation могут использовать для своего содержимого по любой причине.

Например, если вы попытаетесь добавить Class объекты в контейнер Foundation (кроме NSMapTable, поскольку этот проект был разработан с большой гибкостью), вы попадете в стену, потому что «современный» ObjC ожидается, что классы наследуют от NSObject или, по крайней мере, реализуют протокол NSObject.

Впрочем, это довольно редкая ситуация. Class является практически единственным полезным классом, который не наследуется от NSObject.

4 голосов
/ 26 октября 2011

Означает ли это, что если тип идентификатора, который не является NSObject или подклассом, добавляется в NSMutableArray, он должен отвечать, чтобы сохранять и освобождать сообщения?

По умолчанию да, хотя существуют обходные пути для этого с использованием CF-API.

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

Это просто, как библиотеки были написаны; Корневые классы (не наследуемые от NSObject) очень необычные. Альтернативой может быть - (void)addObject:(id<NSObject>), но для этого потребуется довольно большое расширение вашего корневого класса ... возможно, лучшим решением был бы протокол NSReferenceCounted, который берет соответствующие биты из NSObject.

Однако типы NS-коллекций действительно предполагают, что они имеют дело с объектами NSO (например, в словарях используются hash и description).

2 голосов
/ 26 октября 2011

Нет, не существует соглашения о том, что объекты типа "id" должны отвечать на сообщения сохранения / освобождения;на самом деле можно сказать, что обеспечение существования таких методов является целью протокола NSObject (а не класса).Однако «id» говорит компилятору «не беспокоить проверку типов», поэтому, когда вы добавляете объект в nsarray, который не реализует эти методы, он скомпилируется, но вы получите сбой во время выполнения.См. http://unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs-id.html для более подробного объяснения.

0 голосов
/ 27 октября 2011

Помимо технических обсуждений выше, я думаю, что ответ связан с историей языка и его предпочтением соглашений по синтаксису.Формальные протоколы изначально не были частью языка - все было неформальным.Впоследствии Apple в основном перешла на формальные протоколы, но неформальные протоколы являются частью языка и используются частями официального API.Поэтому они являются полностью поддерживаемой частью первого класса Objective-C.

Если вы посмотрите на документацию по NSArray, в ней, среди прочего, говорится:

В большинствеслучаи ваш пользовательский класс NSArray должен соответствовать соглашениям о владении объектами Cocoa.Таким образом, вы должны отправлять сохранение каждому объекту, который вы добавляете в свою коллекцию, и отпускать каждому объекту, который вы удаляете из коллекции.Конечно, если причиной создания подкласса NSArray является реализация поведения удержания объекта, отличного от нормы (например, невыдерживающий массив), то вы можете игнорировать это требование.

И:

NSArray «соединен бесплатно» со своим аналогом Core Foundation, CFArray [...], вы можете иногда делать вещи с CFArray, которые вы не можете легко сделать с NSArray.Например, CFArray предоставляет набор обратных вызовов, некоторые из которых предназначены для реализации пользовательского поведения retain-release.Если вы укажете реализации NULL для этих обратных вызовов, вы можете легко получить невыдерживающий массив.

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

id <NSObject> правильно не используется, поскольку протокол NSObject не соответствует протоколу, который объекты должны реализовывать, чтобы его можно было использовать с NSArray.Вместо этого в документации установлен неформальный протокол, который является подмножеством NSObject (хотя при втором удалении неформальности, что редко).Хотя неофициальные протоколы становятся все более редкими, они действительны и не являются исключительными.

Итак, короткая версия моего ответа: NSArray документирует неофициальный протокол, этот протокол не совпадает с NSObject.Неформальные протоколы не представлены в синтаксисе, поэтому id.

0 голосов
/ 26 октября 2011

Если вам действительно нужен массив, содержащий элементы, которые вы не хотите сохранять / освобождать, вы можете создать его с помощью CFArrayCreateMutable и передать ему соответствующие обратные вызовы. (CFMutableArray бесплатный для моста NSMutableArray.)

0 голосов
/ 26 октября 2011

Не следует помещать необъектные типы в NSArray, например, если вы хотите сохранить массив CGRects, тогда вы заключаете их в NSValue объекты и сохраняете их (это то же самое для CGPoint, CGSize и т. д.).

Если у вас есть пользовательская структура, вам понадобится NSObject дочерняя оболочка, которая в первую очередь побеждает цель!

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