Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazily named class wasn't named by lazy name handler

The warning below gets occasionally reported in the Xcode console. What does this warning mean and how do I stop it from happening?

objc[4082]: Lazily named class 0x7ffee3d07ba0 wasn't named by lazy name handler
like image 450
Adam Dunmars Avatar asked Nov 18 '20 01:11

Adam Dunmars


1 Answers

This error happens when the name of a lazily named Objective-C class is requested and either lazy class namer hook is not set or it returns NULL name for the given lazily named class.


What is lazy class namer hook?

The hook is just a pointer to a function of type objc_hook_lazyClassNamer:

typedef const char * _Nullable (*objc_hook_lazyClassNamer)(_Nonnull Class);

The function takes an Objective-C Class and returns a name for it. As simple as that. You can set your own lazy class namer with help of 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);
    ...

As you can see, the second argument allows you to take a pointer to the original class namer. The contract requires you to forward request to the original namer if your own implementation failed to provide the name for the class (for classes your implementation is not responsible for/not aware of).

What is lazily named Objective-C class?

Sophisticated Objective-C developers are probably familiar with a structure which describes an Objective-C class:

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;

As you can see at the time of writing the structure is no longer used, however it gives quite a good idea of what an Objective-C class consists of. Roughly saying, the name member variable is used when requesting a class name with functions class_getName, object_getClassName and NSStringFromClass. In modern Objective-C a class is not required to have this variable set when being instantiated, and the classes which don't have the name variable set are exactly what is called a lazily named Objective-C class.

Unfortunately I don't know what is the motivation behind having such classes, but you will barely find them when working with UIKit or Foundation frameworks, so it's not something you may have to deal with on daily basis. The lazily named classes tend to appear, however, when you retain instances of classes defined Swift programming language in Objective-C.

How do I create a lazily named Objective-C class?

Long story short - it can't be done with standard Objective-C runtime functions, and you usually don't want to do that (objc_allocateClassPair cannot be called without name argument provided)

For academic research, you can mimic an Objective-C class structure and trick the Objective-C runtime functions into thinking it's an Objective-C class with a bridge cast.

First define the structure which describes a class layout like this:

struct LazyNameClassLayout {
    struct LazyNameClassLayout *isa;
    struct LazyNameClassLayout *superclass;
    void *cachePtr;
    uintptr_t zero;
    uintptr_t roSection;
};

The roSection is a pointer to so-called RO (read-only) data structure where the name variable is located in. As we are not required to make a complete class (but rather where the name variable is not provided), you can simply make an empty structure to define this section:

struct LazyNameClassLayoutRO {
} LazyNameMetaclassRO; 

We use the LazyNameMetaclassRO to instantiate a metaclass for our future lazily named class. Before it can be done, however, we need to introduce a couple of "magic" definitions which comes out of Objective-C runtime:

extern struct objc_cache _objc_empty_cache;
extern struct LazyNameClassLayout OBJC_METACLASS_$_NSObject;
extern struct LazyNameClassLayout OBJC_CLASS_$_NSObject;

Now we are ready to define our own Objective-C lazily named classes. Let's start with metaclass:

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

And the class itself:

struct LazyNameClassLayoutRO LazyNameClassRO = {};

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

And we are done, such impostor is enough to make a naming functions think it's an Objective-C class. The following code will print "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;
}

Feel free to refer to the gist with the complete example listing if you struggle at any step.

like image 147
The Dreams Wind Avatar answered Nov 15 '22 21:11

The Dreams Wind