懒惰命名的类不是由懒惰的名称处理程序命名的

Ada*_*ars 11 objective-c

我在 X-Code 中看到的这个警告是什么意思,我该如何阻止它;_;。哇它烦人。

Lazily named class 0x7ffee3d07ba0 wasn't named by lazy name handler
Run Code Online (Sandbox Code Playgroud)

The*_*ind 2

当请求延迟命名的 Objective-C 类的名称并且未设置延迟类命名器挂钩返回给NULL定延迟命名类的名称时,会发生此错误。


什么是惰性类名钩子?

钩子只是一个指向类型函数的指针objc_hook_lazyClassNamer

typedef const char * _Nullable (*objc_hook_lazyClassNamer)(_Nonnull Class);
Run Code Online (Sandbox Code Playgroud)

该函数采用Objective-CClass并返回它的名称。就如此容易。您可以在以下命令的帮助下设置自己的惰性类名称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, &OrigNamer);
    ...
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,第二个参数允许您获取指向原始类名的指针。如果您自己的实现无法提供类的名称(对于您的实现不负责/不知道的类),合同要求您将请求转发给原始命名者。

什么是惰性命名的 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;
Run Code Online (Sandbox Code Playgroud)

正如您在撰写本文时所看到的,该结构已不再使用,但它很好地说明了 Objective-C 类的组成。粗略地说,成员变量是在请求带有函数、和 name的类名时使用的。在现代 Objective-C 中,类在实例化时不需要设置此变量,而没有设置变量的类正是所谓的惰性命名 Objective-C 类class_getNameobject_getClassNameNSStringFromClassname

不幸的是,我不知道拥有此类类背后的动机是什么,但在使用UIKitFoundation框架时你几乎找不到它们,所以这不是你每天必须处理的事情。然而,当您保留在 Objective-C 中定义的 Swift 编程语言的类实例时,往往会出现延迟命名的类。

如何创建一个延迟命名的 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;
};
Run Code Online (Sandbox Code Playgroud)

roSection是一个指向变量所在的所谓 RO(只读)数据结构的指针name。由于我们不需要创建一个完整的类(而是name不提供变量的类),因此您可以简单地创建一个空的类定义此部分的结构:

struct LazyNameClassLayoutRO {
} LazyNameMetaclassRO; 
Run Code Online (Sandbox Code Playgroud)

我们使用LazyNameMetaclassRO来为我们未来的延迟命名类实例化一个元类。然而,在完成之前,我们需要引入一些来自 Objective-C 运行时的“神奇”定义:

extern struct objc_cache _objc_empty_cache;
extern struct LazyNameClassLayout OBJC_METACLASS_$_NSObject;
extern struct LazyNameClassLayout OBJC_CLASS_$_NSObject;
Run Code Online (Sandbox Code Playgroud)

现在我们准备定义我们自己的 Objective-C 惰性命名类。让我们从元类开始:

struct LazyNameClassLayout LazyNameMetaclass = {
    .isa = &OBJC_METACLASS_$_NSObject,
    .superclass = &OBJC_METACLASS_$_NSObject,
    .cachePtr = &_objc_empty_cache,
    .roSection = (uintptr_t)&LazyNameMetaclassRO,
};
Run Code Online (Sandbox Code Playgroud)

以及课程本身:

struct LazyNameClassLayoutRO LazyNameClassRO = {};

struct LazyNameClassLayout LazyNameClass = {
    .isa = &LazyNameMetaclass,
    .superclass = Nil,
    .cachePtr = &_objc_empty_cache,
    .roSection = (uintptr_t)&LazyNameClassRO,
};
Run Code Online (Sandbox Code Playgroud)

我们完成了,这样的冒名顶替者足以让命名函数认为它是一个 Objective-C 类。以下代码将打印“Lazy Name”:

static objc_hook_lazyClassNamer OrigNamer;

static const char *ClassNamer(Class cls) {
    if (cls == (__bridge Class)(&LazyNameClass)) {
        return "Lazy Name";
    }
    return OrigNamer(cls);
}

int main(int argc, const char * argv[]) {
    objc_setHook_lazyClassNamer(ClassNamer, &OrigNamer);
    printf("%s\n", class_getName([(__bridge Class)&LazyNameClass class]));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果您在任何步骤中遇到困难,请随时参考完整示例列表的要点