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