Обработка ответа Кевина:
Когда вы объявляете класс, например:
@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;
или
self->privateIvar = someValue;
переводитсякак что-то вроде:
*(self + offsetOfPrivateIvar) = someObject;
Аналогично, за пределами класса
SomeClass *obj = [SomeClass new];
obj->publicIvar = someObject;
переводится как-то вроде:
SomeClass *obj = [SomeClass new];
*(obj + offsetOfPublicIvar) = someObject;
Однако компилятор позволяетэто в соответствии с видимостью переменной экземпляра:
- На частную переменную экземпляра можно ссылаться только в реализации соответствующего класса;
- На защищенную переменную экземпляра можно ссылаться только вреализация соответствующего класса и его подклассов;
- На общедоступную переменную экземпляра можно ссылаться в любом местеere.
Когда переменная экземпляра объявляется в расширении класса, например,
@interface SomeClass () {
id extensionIvar;
}
@end
, компилятор добавляет его в макет переменной экземпляра:
+-----+------------+-----+---------------+
| ... | otherIvars | ... | extensionIvar |
+-----+------------+-----+---------------+
и любая ссылка на эту переменную экземпляра заменяется соответствующим смещением относительно экземпляра.Однако, поскольку эта переменная экземпляра известна только файлу реализации, в котором было объявлено расширение класса, компилятор не позволит другим файлам ссылаться на него.Произвольный исходный файл может ссылаться только на те переменные экземпляра, которые ему известны (с соблюдением правил видимости).Если переменные экземпляра объявлены в заголовочном файле, который импортируется исходным файлом, то исходный файл (или, точнее, компилятор при переводе этого модуля) узнает о них.
С другой стороны,Переменная расширения известна только исходному файлу, в котором она была объявлена.Таким образом, мы можем сказать, что переменные экземпляра, объявленные в расширениях класса, скрыты от других файлов.То же самое относится и к вспомогательным переменным экземпляров свойств, объявленных в расширениях классов.Это похоже на @private
, но более ограничительно.
Обратите внимание, что во время выполнения правила видимости не применяются.Используя кодирование значения ключа, произвольный исходный файл может иногда (правила описаны здесь ) обращаться к переменной экземпляра:
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.