Размер указателя типа Foo *
зависит от целевой платформы.На большинстве современных платформ Apple это 64-битная версия, но Apple Watch до Series 4 32-битная.
Существует несколько контекстов, в которых вы можете написать это:
Foo *foo;
Вы можете написать это как глобальную переменную вне любого объявления @interface
или @implementation
и вне какой-либо функции.Затем каждый раз, когда программа запускается, она выделяет место для одного указателя и устанавливает для этого указателя значение nil.
Вы можете написать это в объявлении переменной @implementation
, например:
@implementation MyObject {
Foo *foo;
}
В этом случае вы объявили переменную экземпляра (или «ivar»).Каждый раз, когда программа создает экземпляр MyObject
, экземпляр включает в себя пространство для одного указателя и устанавливает указатель на ноль.
Вы можете записать это как локальную переменную в функции илиметод, подобный этому:
- (void)doSomething {
Foo *foo;
}
В этом случае вы объявили локальную переменную.Каждый раз, когда вызывается функция или метод, он выделяет один указатель в своем фрейме стека и (при условии, что вы скомпилировали с включенным ARC, что является значением по умолчанию) инициализирует указатель равным nil.
Обратите внимание, что во всех этих случаях foo
не указывает на экземпляр Foo
.Это указывает на ноль.Чтобы foo
указывал на экземпляр Foo
, вы должны установить для него ссылку на Foo
, которую вы получили откуда-то еще.Вы можете получить эту ссылку, вызвав функцию или метод, например:
- (void)doSomething {
Foo *foo;
// foo is nil here.
foo = [[Foo alloc] init];
// If [[Foo alloc] init] succeeded, then foo now points to an
// instance of Foo. If [[Foo alloc] init] returned nil, which
// indicates failure, then foo is still nil.
}
Или вам может быть передана ссылка Foo
в качестве аргумента функции:
- (void)doSomethingWithFoo:(Foo *)incomingFoo {
Foo *foo;
// foo is nil here.
foo = incomingFoo;
// foo now points to whatever incomingFoo points to, which should be
// either an instance of Foo, or nil.
}
Или выможет получить его из некоторой другой глобальной, локальной или переменной экземпляра.
Относительно того, «Как именно компилятор отмечает или сигнализирует о том, что указатель фактически хранит ссылку на память типа Foo
»Это не так.Во время компиляции компилятор знает, что foo
должен указывать только на Foo
(или ноль), и пытается помешать вам присвоить его вещам, которые не являются указателями на Foo
.Например, компилятор выдаст предупреждение или ошибку для этого:
Foo *foo = @"hello";
, потому что компилятор знает, что NSString
не является Foo
.(Я предполагаю, что вы не сделали Foo
typedef
или подкласс NSString
.)
Однако вы можете переопределить проблемы с типом компилятора, используя приведение:
Foo *foo = (Foo *)@"hello";
или с помощью типа id
:
id something = @"hello";
Foo *foo = something;
Компилируется и работает нормально, пока вы не попытаетесь что-то сделать с foo
, что NSString
не знает, как это сделать.
Так что не компилятор знает, что «указатель действительно хранит ссылку на память типа Foo
».
Среда выполнения Objective C знает, что указатель действительно хранитссылка на Foo
.Чтобы понять, как среда выполнения отслеживает тип объекта, сначала необходимо узнать об объекте класса Foo
.
Для каждого класса Objective-C в программе существует во время выполнения один специальный объектназывается «объект класса».Таким образом, для NSObject
существует один объект класса NSObject
, а для NSString
- один объект класса NSString
, а для Foo
- один объект класса Foo
.Обратите внимание, что в общем случае объект класса не является экземпляром самого себя!То есть объект класса NSString
сам по себе не является экземпляром NSString
, а объект класса Foo
сам по себе не является экземпляром Foo
.
Объект класса Foo
знает, чтосоставляет экземпляр Foo
:
- Суперкласс
Foo
(может быть NSObject
, может быть что-то еще). - Имя, тип и размер каждогопеременная экземпляра
Foo
(кроме тех, которые унаследованы от суперкласса Foo
). - Имя, подпись типа и адрес реализации каждого сообщения, понятного для
Foo
(кроме тех, которые унаследованы отсуперкласс Foo
).
Первыйбайты каждого объекта Objective-C содержат указатель на объект класса. 1 Этот указатель называется указателем isa
и определяет тип объекта.Когда вы используете синтаксис, который отправляет сообщение объекту, например [foo length]
, компилятор генерирует вызов objc_msgSend
.Напомним, что в [foo length]
объект, на который ссылается foo
, называется приёмник .Функция objc_msgSend
использует указатель isa
получателя, чтобы найти объект класса получателя.Он просматривает таблицу сообщений объекта класса, чтобы найти реализацию length
и переходит к ней.Если Foo
не определяет сообщение length
, то objc_msgSend
ищет сообщение в суперклассе Foo
и т. Д. В цепочке суперклассов. 2
Итак, именно этот isa
указатель определяет тип объекта во время выполнения.
Так как же выделяется Foo
объект?Когда вы говорите [[Foo alloc] init]
, это означает, что «отправьте сообщение alloc
объекту класса Foo
, а затем отправьте сообщение init
тому, что возвращается из alloc
».Таким образом, объект класса Foo
получает сообщение alloc
.Но объект класса Foo
, вероятно, не реализует alloc
напрямую.Он наследует NSObject
реализацию alloc
.
Так что +[NSObject alloc]
фактически выделяет память для нового Foo
.Как вы говорите, он выделяет «непрерывный сегмент памяти», но он не «неинициализирован и напечатан как Foo
».Это инициализированный и тип Foo
. Документация +[NSObject alloc]
гласит:
Переменная экземпляра isa
нового экземпляра инициализируется в структуру данных, которая описывает класс;память для всех остальных переменных экземпляра установлена в 0.
Вы можете посмотреть на реализацию здесь .Это функция callAlloc
.Он использует стандартную библиотечную функцию C calloc
для выделения памяти, а calloc
заполняет память до 0. Затем он устанавливает указатель isa
.
относительно того, «Как он знает, сколько памятивыделить »: помните, что каждый объект класса знает все переменные экземпляра своих экземпляров.Таким образом, чтобы выделить Foo
, +[NSObject alloc]
суммирует размеры всех переменных экземпляра Foo
, плюс размеры всех переменных экземпляра суперкласса Foo
, рекурсивно до конца суперклассацепь.Это говорит о том, сколько байтов выделить.За исключением того, что это будет слишком медленно, чтобы делать это каждый раз, когда он выделяет объект.Таким образом, программа предварительно вычисляет размер экземпляра для каждого объекта класса при запуске, и +[NSObject alloc]
ищет предварительно вычисленный размер Foo
в Foo
объекте класса.
Если объект не представлен в виде тегового указателя , но не беспокойтесь об этом.
Если вы хотите знать, что происходит, когда objc_msgSend
достигнув конца цепочки суперкласса, не найдя реализации, прочитайте Пересылка сообщений Objective C .