欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

What's the methodLists attribute of the structure objc_class for?

程序员文章站 2022-06-02 22:14:10
...

在*上看到这个问题,刚好那时候也在看相关内容,同有疑惑。

不过后来一时没理清,暂时先搁置了下来。今天早上,同事突然问起了这个问题,所以又跟了一下。

具体过程直接用英文写。


I'm now using Xcode 4, and the declaration of struct objc_class is as following:

struct objc_class {
    Class isa;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */


The macro OBJC2_UNAVAILABLE makes me know that struct objc_class, under Objective-C 2.0, is just a pointer to itself, similar to struct objc_object:

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

/* OBJC2_UNAVAILABLE: unavailable in objc 2.0, deprecated in Leopard */
#if !defined(OBJC2_UNAVAILABLE)
#   if __OBJC2__
#       define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
#   else
#       define OBJC2_UNAVAILABLE DEPRECATED_IN_MAC_OS_X_VERSION_10_5_AND_LATER
#   endif
#endif


Therefore I failed when I tried to follow the methodLists to observe its changes, which results in EXEC_BAD_ACCESS.

I realized I should find out the new struct of objc_class, then occasionally the file objc-runtime-new.h came into my eyes with struct class_t:

typedef struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    class_rw_t *data;
} class_t;


The member vtable is dazzling for C++ programmers, including me. And methodLists sits in data:

typedef struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;
    
    method_list_t **methods;
    struct chained_property_list *properties;
    const protocol_list_t ** protocols;

    struct class_t *firstSubclass;
    struct class_t *nextSiblingClass;
} class_rw_t;


So, why method_list_t **, why notmethod_list_t *?

What'sstruct objc_method_list **methodListsfor?

objc-class.m states:

 * cls->methodLists may be in one of three forms:
 * 1. NULL: The class has no methods.
 * 2. non-NULL, with CLS_NO_METHOD_ARRAY set: cls->methodLists points 
 *    to a single method list, which is the class's only method list.
 * 3. non-NULL, with CLS_NO_METHOD_ARRAY clear: cls->methodLists points to 
 *    an array of method list pointers. The end of the array's block 
 *    is set to -1. If the actual number of method lists is smaller 
 *    than that, the rest of the array is NULL.
 * 
 * Attaching categories and adding and removing classes may change 
 * the form of the class list. In addition, individual method lists 
 * may be reallocated when fixed up.
 *
 * Classes are initially read as #1 or #2. If a category is attached 
 * or other methods added, the class is changed to #3. Once in form #3, 
 * the class is never downgraded to #1 or #2, even if methods are removed.
 * Classes added with objc_addClass are initially either #1 or #3.


For a normal class, #1 or #2 is the proper situation, but when class_addMethod is used on or categories are added to it, the class changes to #3.

It is easy to understand #1 & #2, NULL or a single method list. But how the class changes to #3?

The key function isattachMethodListsin objc_runtime-new.mm, when calling class_addMethod or adding a category, this function will be called.

staticvoid 
attachMethodLists(class_t *cls, method_list_t **addedLists, int addedCount, 
                  BOOL methodsFromBundle, BOOL *inoutVtablesAffected)
{
    rwlock_assert_writing(&runtimeLock);

    // Don't scan redundantly
    BOOL scanForCustomRR = !UseGC && !cls->hasCustomRR();

    // Method list array is NULL-terminated.
    // Some elements of lists are NULL; we must filter them out.

    method_list_t **oldLists = cls->data()->methods;
    int oldCount = 0;
    if (oldLists) {
        while (oldLists[oldCount]) oldCount++;
    }
        
    int newCount = oldCount + 1;  // including NULL terminator
    for (int i = 0; i < addedCount; i++) {
        if (addedLists[i]) newCount++;  // only non-NULL entries get added
    }

    method_list_t **newLists = (method_list_t **)
        _malloc_internal(newCount * sizeof(*newLists));

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    newCount = 0;
    int i;
    for (i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        if (!mlist) continue;

        // Do sth. necessary
        …code omitted
        
        // Fill method list array
        newLists[newCount++] = mlist;
    }

    // Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }
    if (oldLists) free(oldLists);

    // NULL-terminate
    newLists[newCount++] = NULL;
    cls->data()->methods = newLists;
}

Below is a draft.

What's the methodLists attribute of the structure objc_class for?