Разрабатывая ответ Кевина:
Когда вы объявляете класс, например:
@interface SomeClass : NSObject {
@public
id publicIvar;
@protected
id protectedIvar;
@private
id privateIvar;
}
@end
компилятор1 выбирает макет переменной экземпляра для этого класса. Этот макет определяет смещения переменных экземпляра относительно адреса экземпляров этого класса. Один из возможных макетов:
+--> publicIvar address = instance address + offsetOfPublicIvar
|
|
+-----+------------+-----+---------------+-----+-------------+-----+
| ... | publicIvar | ... | protectedIvar | ... | privateIvar | ... |
+-----+------------+-----+---------------+-----+-------------+-----+
|
|
+--> instance address
Когда в коде есть ссылка на переменную экземпляра — либо в реализации класса, либо в какой-либо другой части базы кода, компилятор заменяет эту ссылку соответствующим смещением переменной экземпляра относительно адреса соответствующего экземпляра.
Например, в реализации SomeClass,
privateIvar = someObject;
or
self->privateIvar = someValue;
переводится примерно так:
*(self + offsetOfPrivateIvar) = someObject;
Точно так же вне класса,
SomeClass *obj = [SomeClass new];
obj->publicIvar = someObject;
переводится примерно так:
SomeClass *obj = [SomeClass new];
*(obj + offsetOfPublicIvar) = someObject;
Однако компилятор разрешает это только в зависимости от видимости переменной экземпляра:
- На приватную переменную экземпляра можно ссылаться только в реализации соответствующего класса;
- На защищенную переменную экземпляра можно ссылаться только в реализации соответствующего класса и его подклассов;
- На общедоступную переменную экземпляра можно ссылаться где угодно.
Когда переменная экземпляра объявлена в расширении класса, например.
@interface SomeClass () {
id extensionIvar;
}
@end
компилятор добавляет его в макет переменной экземпляра:
+-----+------------+-----+---------------+
| ... | otherIvars | ... | extensionIvar |
+-----+------------+-----+---------------+
и любая ссылка на эту переменную экземпляра заменяется соответствующим смещением по отношению к экземпляру. Однако, поскольку эта переменная экземпляра известна только в файле реализации, в котором было объявлено расширение класса, компилятор не позволит другим файлам ссылаться на нее. Произвольный исходный файл может ссылаться только на известные ему переменные экземпляра (с соблюдением правил видимости). Если переменные экземпляра объявлены в заголовочном файле, который импортируется исходным файлом, то исходный файл (или, точнее, компилятор при переводе этого модуля) знает о них.
С другой стороны, переменная расширения известна только в исходном файле, где она была объявлена. Таким образом, мы можем сказать, что переменные экземпляра, объявленные в расширениях класса, скрыты от других файлов. Те же рассуждения применимы к резервным переменным экземпляра свойств, объявленных в расширениях класса. Он похож на @private
, но более строгий.
Обратите внимание, однако, что правила видимости во время выполнения не применяются. Используя Key-Value Coding, произвольный исходный файл может иногда (правила описаны здесь) доступ к переменной экземпляра:
SomeClass *obj = [SomeClass new];
id privateValue = [obj valueForKey:@"privateIvar"];
включая переменную экземпляра, объявленную в расширении:
id extensionValue = [obj valueForKey:@"extensionIvar"];
Независимо от KVC, доступ к переменным экземпляра осуществляется через API среды выполнения Objective-C:
Ivar privateIvar = class_getInstanceVariable([SomeClass class],
"privateIvar");
Ivar extensionIvar = class_getInstanceVariable([SomeClass class],
"extensionIvar");
id privateValue = object_getIvar(obj, privateIvar);
id extensionValue = object_getIvar(obj, extensionIvar);
Обратите внимание, что класс может иметь более одного расширения класса. Однако одно расширение класса не может объявлять переменную экземпляра с тем же именем, что и другая переменная экземпляра, включая переменные экземпляра, объявленные в других расширениях класса. Поскольку компилятор выдает такие символы, как:
_OBJC_IVAR_$_SomeClass.extensionIvar
для каждой переменной экземпляра наличие разных расширений, объявляющих переменные экземпляра с одним и тем же именем, не приводит к ошибке компилятора, поскольку данный исходный файл не знает о другом исходном файле, но вызывает ошибку компоновщика.
1Этот макет может быть изменен средой выполнения Objective-C. На самом деле смещения вычисляются компилятором и сохраняются как переменные, и среда выполнения может изменить их по мере необходимости.
PS: Не все в этом ответе применимо ко всем версиям компилятора/среды выполнения. Я рассматривал только Objective-C 2.0 с нехрупким ABI и последними версиями Clang/LLVM.
person
Community
schedule
26.04.2011