В Objective C, почему [object doSomething]
? Не был бы это быть [*object doSomething]
так как Вы называете метод на объекте?, который означает, что необходимо разыменовать указатель?
Ответ восходит к корням 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, которая содержит ваши переменные экземпляра как слоты в этой структуре, объединенные со слотами для переменных экземпляра для всех суперклассов.(Современная среда выполнения работает немного по-другому, так что суперкласс может иметь добавленные ivars без необходимости перекомпиляции всех подклассов).
Теперь рассмотрим разницу между этими двумя переменными:
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 - метода. что в конечном итоге выполняется.
Если вы действительно хотите углубиться, начните здесь . Но не беспокойтесь, пока вы полностью не разобрались с этим .
Поскольку objc_msgSend () объявлен следующим образом:
id objc_msgSend(id theReceiver, SEL theSelector, ...)
Во время выполнения Objective-C может понадобиться передать объект в пару различных функций, поэтому ему нужна ссылка на объект, а не на сам объект.
Объект в Objective-C по сути является структурой
. Первым членом структуры
является класс isa
(общий размер структуры
можно определить с помощью isa
). Последующие члены структуры
могут включать в себя переменные экземпляра.
Когда вы объявляете объект Objective-C, вы всегда объявляете его как тип указателя, потому что среда выполнения передает ваш объект другим методам и функциям; если они изменят какие-либо члены структуры
(путем изменения переменных экземпляра и т. д.), они будут «применяться» ко всем ссылкам на ваш объект, а не только локально внутри метода или функции.
Вы никогда не упускаете из виду указатели на объекты, период. Тот факт, что они набираются как указатели, а не просто "типы объектов", является артефактом наследия языка C. Это в точности эквивалентно системе типов Java, где доступ к объектам всегда осуществляется через ссылки. Вы никогда не разыменовываете ссылку на объект в Java - на самом деле, вы не можете. Вы не должны думать о них как о указателях, потому что с семантической точки зрения это не так. Это просто ссылки на объекты.
Частично причина в том, что вы будете получать исключения нулевого указателя слева и справа. Отправка сообщения на nil
разрешена и часто совершенно законна (ничего не делает и не генерирует ошибку).
Но вы можете думать об этом как об аналоге нотации C ++ ->
: она выполняет метод и разыменовывает указатель в одном куске синтаксического сахара.
Я бы сказал так: Что язык ассоциируется с серией алфавитов - это просто конвенция. Люди, разработавшие Objective-C, решили, что
[x doSomething];
означает "отправку сообщения doSomething
объекту , на который указывает x". Они определили его таким образом, вы следуете правилу :).
Одна из особенностей Objective-C, по сравнению, например, с C++, заключается в том, что он не имеет синтаксиса для удержания самого объекта, а не указателя на объект. Таким образом,
NSString* string;
- это нормально, но
NSString string;
- это противозаконно. Если бы это было возможно, то должен был бы быть способ "послать сообщение с заглавной буквыString
на строку строку
", а не "послать сообщение с заглавной буквыString
на строку , на которую указывает строка
". Но на самом деле, вы всегда посылаете сообщение объекту , на который указывает переменная в вашем исходном коде.
Итак, если бы разработчики Objective-C следовали вашей логике, вам пришлось бы писать
[*x doSomething];
каждый раз, когда вы посылаете сообщение... Видите ли, *
должно появляться всегда после ведущей скобки [
, образуя комбинацию [*
. На этом этапе, я полагаю, Вы согласны с тем, что лучше перестроить язык так, чтобы вместо [*
нужно было только написать [
, изменив значение последовательности букв [x doSomething]
.
Вы не должны на самом деле думать об этом как о указателях на объекты. Это своего рода историческая деталь реализации, заключающаяся в том, что они являются указателями и что вы используете их таким образом в синтаксисе отправки сообщений (см. Ответ @ bbum). Фактически, это просто «идентификаторы объектов» (или ссылки). Давайте немного перемотаем назад, чтобы увидеть концептуальное обоснование.
Objective-C был впервые предложен и обсужден в этой книге: Объектно-ориентированное программирование: эволюционный подход . Это не очень практично для современных программистов Cocoa, но мотивация для языка там.
Обратите внимание, что в книге все объекты имеют тип id
. Вы вообще не видите более конкретных Object *
в книге; это просто утечка в абстракции, когда мы говорим о «почему». Вот что говорится в книге:
Идентификаторы объектов должны однозначно идентифицировать столько объектов, сколько они могут сосуществовать в системе одновременно. Они хранятся в локальных переменных, передаются как аргументы в выражениях сообщений и в вызовах функций, хранятся в переменных экземпляра (поля внутри объектов) и в других типах структур памяти. Другими словами, их можно использовать так же легко, как и встроенные типы базового языка.
То, как идентификатор объекта на самом деле идентифицирует объект, является деталью реализации, для которой возможны многие варианты. Разумный выбор, безусловно, один из самых простых и тот, который используется в Objective-C, - использовать физический адрес объекта в памяти в качестве его идентификатора.Objective-C сообщает об этом решении C, генерируя оператор typedef в каждом файле. Это определяет новый тип, id, в терминах другого типа, который C уже понимает, а именно указателей на структуры. [...]
Идентификатор занимает фиксированное количество места. [...] Это пространство не совпадает с пространством, занимаемым личными данными в самом объекте.
(pp58-59, 2nd ed.)
Итак, ответ на ваш вопрос двоякий:
Строго типизированный синтаксис, в котором вы говорите «объект конкретно типа NSString» и, таким образом, используете NSString *
, является более современным изменением и в основном является вариантом реализации, эквивалентным id
.
Если это кажется возвышенным ответом на вопрос о разыменовании указателей, важно помнить, что объекты в Objective-C являются «особенными» в соответствии с определением языка. Они реализованы как структуры и передаются как указатели на структуры, но концептуально они различны.