Лениво названный класс не был назван ленивым обработчиком имен

#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;
}
  

Не стесняйтесь обращаться к сути с полным списком примеров, если у вас возникли проблемы на каком-либо этапе.