#objective-c
#objective-c #objective-c-runtime
Вопрос:
О приведенном ниже предупреждении иногда сообщается в консоли Xcode. Что означает это предупреждение и как мне остановить его?
objc[4082]: Lazily named class 0x7ffee3d07ba0 wasn't named by lazy name handler
Комментарии:
1. Когда вы его получили. Какая строка в коде вызвала предупреждения?
2. Здесь та же проблема. Я вижу это в выводе консоли, когда я устанавливаю точку останова во второй строке этого кода: 01 ‘NSRange workingCharRange = NSMakeRange(workingPosition, 1);’ 02 ‘NSString *workingChar = [someString substringWithRange:workingCharRange];’ Если я очищу точку останова и просто позволю коду выполняться, нетсообщение консоли.
3. Для меня он появляется в точке останова … в коде C . Возможно, мы наделяем Xcode большим интеллектом, чем он того требует. «Класс с ленивым именем», вероятно, вообще не является классом; Я подозреваю, что lldb что- то путают. Пока не могу понять, что именно.
Ответ №1:
Эта ошибка возникает, когда запрашивается имя класса Objective-C с ленивым именем, и либо не установлен хук для имени ленивого класса, либо он возвращает NULL
имя для данного класса с ленивым именем.
Что такое перехват имени ленивого класса?
Перехват — это просто указатель на функцию типа objc_hook_lazyClassNamer
:
typedef const char * _Nullable (*objc_hook_lazyClassNamer)(_Nonnull Class);
Функция принимает Objective-C Class
и возвращает для него имя. Вот так просто. Вы можете задать свой собственный ленивый класс с помощью objc_setHook_lazyClassNamer
:
static objc_hook_lazyClassNamer OrigNamer;
static const char *ClassNamer(Class cls) {
const char *name = OrigNamer(cls);
printf("A lazily named class: %s!n", name);
return name;
}
int main(int argc, const char * argv[]) {
objc_setHook_lazyClassNamer(ClassNamer, amp;OrigNamer);
...
Как вы можете видеть, второй аргумент позволяет вам использовать указатель на исходный имя класса. Контракт требует, чтобы вы пересылали запрос исходному имени, если ваша собственная реализация не смогла предоставить имя для класса (для классов, за которые ваша реализация не несет ответственности / не знает).
Что такое класс с ленивым именем Objective-C?
Опытные разработчики Objective-C, вероятно, знакомы со структурой, которая описывает класс Objective-C.:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Как вы можете видеть на момент написания структура больше не используется, однако она дает довольно хорошее представление о том, из чего состоит класс Objective-C. Грубо говоря, name
переменная-член используется при запросе имени класса с функциями class_getName
, object_getClassName
и NSStringFromClass
. В современном name
Objective-C классу не требуется устанавливать эту переменную при создании экземпляра, а классы, у которых нет набора переменных, являются именно тем, что называется классом Objective-C с ленивым именем.
К сожалению, я не знаю, какова мотивация для создания таких классов, но вы вряд ли найдете их при работе с UIKit
Foundation
фреймворками or, так что это не то, с чем вам, возможно, придется иметь дело ежедневно. Однако классы с ленивыми именами имеют тенденцию появляться, когда вы сохраняете экземпляры классов, определенных языком программирования Swift в Objective-C.
Как мне создать класс Objective-C с ленивым именем?
Короче говоря, это невозможно сделать с помощью стандартных функций среды выполнения Objective-C, и вы обычно не хотите этого делать ( objc_allocateClassPair
не может быть вызван без name
предоставленного аргумента)
Для академических исследований вы можете имитировать структуру класса Objective-C и обмануть функции среды выполнения Objective-C, заставив их думать, что это класс Objective-C с приведением моста.
Сначала определите структуру, которая описывает макет класса следующим образом:
struct LazyNameClassLayout {
struct LazyNameClassLayout *isa;
struct LazyNameClassLayout *superclass;
void *cachePtr;
uintptr_t zero;
uintptr_t roSection;
};
Это roSection
указатель на так называемую RO (доступную только для чтения) структуру данных, в которой name
находится переменная. Поскольку мы не обязаны создавать полный класс (а скорее там, где name
переменная не указана), вы можете просто создать пустую структуру для определения этого раздела:
struct LazyNameClassLayoutRO {
} LazyNameMetaclassRO;
Мы используем LazyNameMetaclassRO
для создания экземпляра метакласса для нашего будущего класса с ленивым именем. Однако, прежде чем это можно будет сделать, нам нужно ввести пару «волшебных» определений, которые появляются во время выполнения Objective-C.:
extern struct objc_cache _objc_empty_cache;
extern struct LazyNameClassLayout OBJC_METACLASS_$_NSObject;
extern struct LazyNameClassLayout OBJC_CLASS_$_NSObject;
Теперь мы готовы определить наши собственные классы с ленивым именем Objective-C. Давайте начнем с метакласса:
struct LazyNameClassLayout LazyNameMetaclass = {
.isa = amp;OBJC_METACLASS_$_NSObject,
.superclass = amp;OBJC_METACLASS_$_NSObject,
.cachePtr = amp;_objc_empty_cache,
.roSection = (uintptr_t)amp;LazyNameMetaclassRO,
};
И сам класс:
struct LazyNameClassLayoutRO LazyNameClassRO = {};
struct LazyNameClassLayout LazyNameClass = {
.isa = amp;LazyNameMetaclass,
.superclass = Nil,
.cachePtr = amp;_objc_empty_cache,
.roSection = (uintptr_t)amp;LazyNameClassRO,
};
И мы закончили, такого самозванца достаточно, чтобы функции именования думали, что это класс Objective-C. Следующий код выведет «Ленивое имя»:
static objc_hook_lazyClassNamer OrigNamer;
static const char *ClassNamer(Class cls) {
if (cls == (__bridge Class)(amp;LazyNameClass)) {
return "Lazy Name";
}
return OrigNamer(cls);
}
int main(int argc, const char * argv[]) {
objc_setHook_lazyClassNamer(ClassNamer, amp;OrigNamer);
printf("%sn", class_getName([(__bridge Class)amp;LazyNameClass class]));
return 0;
}
Не стесняйтесь обращаться к сути с полным списком примеров, если у вас возникли проблемы на каком-либо этапе.