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