Ответ возвращается к C-корням Objective-C.Objective-C изначально был написан как препроцессор для компилятора C. То есть Objective-C был скомпилирован не так сильно, как был преобразован в прямой C, а затем скомпилирован.
Начнем с определениявведите id
.Он объявлен как:
typedef struct objc_object {
Class isa;
} *id;
То есть id
- это указатель на структуру, первое поле которой имеет тип Class (который сам по себе является указателем на структуру, определяющую класс),Теперь рассмотрим NSObject
:
@interface NSObject <NSObject> {
Class isa;
}
Обратите внимание, что компоновка NSObject
и компоновка типа, на который указывает id
, идентичны.Это происходит потому, что в действительности экземпляр объекта Objective-C на самом деле является просто указателем на структуру, первое поле которой - всегда указатель - указывает на класс, который содержит методы для этого экземпляра (наряду с некоторыми другими метаданными).
Когда вы создаете подкласс NSObject и добавляете некоторые переменные экземпляра, вы, для всех намерений и целей, просто создаете новую структуру C, которая содержит ваши переменные экземпляра как слоты в этой структуре, объединенные в слоты для переменных экземплярадля всех суперклассов.(Современная среда выполнения работает немного по-другому, так что суперкласс может иметь добавленные ивары, не требуя перекомпиляции всех подклассов).
Теперь рассмотрим разницу между этими двумя переменными:
NSRect foo;
NSRect *bar;
(NSRect - простая структура C - ObjC не задействован).foo
создается с хранилищем в стеке.Он не выживет после закрытия фрейма стека, но вам также не нужно освобождать память.bar
- это ссылка на структуру NSRect, которая, скорее всего, была создана в куче с использованием malloc()
.
Если вы попытаетесь сказать:
NSArray foo;
NSArray *bar;
Компилятор будет жаловатьсяо первом, говоря что-то вроде стековых объектов, не допускается в Objective-C .Другими словами, все Объекты Objective C должны быть выделены из кучи (более или менее - есть одно или два исключения, но они сравнительно эзотеричны для этого обсуждения), и, в результате, вы всегда относится к объекту через адрес указанного объекта в куче;вы всегда работаете с указателями на объекты (а тип id
на самом деле является просто указателем на любой старый объект).
Возвращаясь к корням препроцессора языка C, вы можете перевести каждый вызов метода наэквивалентная строка C. Например, следующие две строки кода идентичны:
[myArray objectAtIndex: 42];
objc_msgSend(myArray, @selector(objectAtIndex:), 42);
Аналогично, метод объявлен так:
- (id) objectAtIndex: (NSUInteger) a;
Эквивалентно объявленной функции Cкак это:
id object_at_index(id self, SEL _cmd, NSUInteger a);
И, глядя на objc_msgSend()
, объявлен первый аргумент типа id
:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...);
И именно поэтому вы не* используйте *foo
в качестве цели вызова метода.Выполните преобразование через вышеприведенные формы - вызов [myArray objectAtIndex: 42]
переводится в вышеуказанный вызов функции C, который затем должен вызывать что-то с эквивалентным объявлением вызова функции C (все в синтаксисе метода).
Ссылка на объект переносится, потому что она дает мессенджеру - objc_msgSend () доступ к классу, чтобы затем найти реализацию метода, а также эту ссылку, становящуюся первым параметром - self - метода, который в конечном итогеВыполнено.
Если вы действительно хотите углубиться, начните здесь .Но не беспокойтесь, пока вы полностью не взяли этот грок .