What's the methodLists attribute of the structure objc_class for?
在*上看到这个问题,刚好那时候也在看相关内容,同有疑惑。
不过后来一时没理清,暂时先搁置了下来。今天早上,同事突然问起了这个问题,所以又跟了一下。
具体过程直接用英文写。
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.