runtime原始碼探究(五)category的載入
main函式開始之前,在一些準備工作之後,libSystem會呼叫
void _objc_init(void)
函式,這裡便是runtime的入口,也就是這時候啟動了runtime。蘋果自己的註釋也描述的很清楚
/*************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
蘋果在這個函式裡面做了一些初始化的工作,包括每個類+load方法的呼叫,關於load方法的呼叫順序如下:
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
接下來蘋果呼叫了map_2_images函式,函式裡面在最後又呼叫了_read_images函式,該函式通過_objc_read_categories_from_image實現category的載入。該函式的實現如下:
static bool _objc_read_categories_from_image (header_info * hi)
{
Module mods;
size_t midx;
bool needFlush = NO ;
if (hi->info()->isReplacement()) {
// Ignore any categories in this image
return NO;
}
// Major loop - process all modules in the header
mods = hi->mod_ptr;
// NOTE: The module and category lists are traversed backwards
// to preserve the pre-10.4 processing order. Changing the order
// would have a small chance of introducing binary compatibility bugs.
midx = hi->mod_count;
while (midx-- > 0) {
unsigned int index;
unsigned int total;
// Nothing to do for a module without a symbol table
if (mods[midx].symtab == nil)
continue;
// Total entries in symbol table (class entries followed
// by category entries)
total = mods[midx].symtab->cls_def_cnt +
mods[midx].symtab->cat_def_cnt;
// Minor loop - register all categories from given module
index = total;
while (index-- > mods[midx].symtab->cls_def_cnt) {
old_category *cat = (old_category *)mods[midx].symtab->defs[index];
needFlush |= _objc_register_category(cat, (int)mods[midx].version);
}
}
return needFlush;
}
首先判斷該類是否忽略載入category,如果忽略就直接返回;然後從傳入的hi(其中儲存的是各個載入的模組的資訊)引數中遍歷要載入的category的所有模組;首先判斷模組的符號表是否為空
// Nothing to do for a module without a symbol table
if (mods[midx].symtab == nil)
continue;
如果為空就不需要載入了;接下來從該模組的符號表中取出category的數量和類的數量,然後遍歷該模組所有的category,呼叫_objc_register_category函式進行載入。該函式的實現如下:
static bool _objc_register_category(old_category *cat, int version)
{
_objc_unresolved_category * new_cat;
_objc_unresolved_category * old;
Class theClass;
// If the category's class exists, attach the category.
if ((theClass = objc_lookUpClass(cat->class_name))) {
return _objc_add_category_flush_caches(theClass, cat, version);
}
// If the category's class exists but is unconnected,
// then attach the category to the class but don't bother
// flushing any method caches (because they must be empty).
// YES unconnected, NO class_handler
if ((theClass = look_up_class(cat->class_name, YES, NO))) {
_objc_add_category(theClass, cat, version);
return NO;
}
// Category's class does not exist yet.
// Save the category for later attachment.
if (PrintConnecting) {
_objc_inform("CONNECT: pending category '%s (%s)'", cat->class_name, cat->category_name);
}
// Create category lookup table if needed
if (!category_hash)
category_hash = NXCreateMapTable(NXStrValueMapPrototype, 128);
// Locate an existing list of categories, if any, for the class.
old = (_objc_unresolved_category *)
NXMapGet (category_hash, cat->class_name);
// Register the category to be fixed up later.
// The category list is built backwards, and is reversed again
// by resolve_categories_for_class().
new_cat = (_objc_unresolved_category *)
malloc(sizeof(_objc_unresolved_category));
new_cat->next = old;
new_cat->cat = cat;
new_cat->version = version;
(void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat);
return NO;
}
蘋果對該函式也進行了說明:
/***********************************************************************
* _objc_register_category.
* Process a category read from an image.
* If the category's class exists, attach the category immediately.
* Classes that need cache flushing are marked but not flushed.
* If the category's class does not exist yet, pend the category for
* later attachment. Pending categories are attached in the order
* they were discovered.
* Returns YES if some method caches now need to be flushed.
**********************************************************************/
如果category對應的類存在,那麼就立即載入category;如果對應的類不存在,那麼將category掛起等待後續載入;如果類的一些方法快取需要重新整理,但是重新整理行為不在該函式中完成,該函式返回YES。
_objc_register_category函式首先去尋找category對應的類
// If the category's class exists, attach the category.
if ((theClass = objc_lookUpClass(cat->class_name))) {
return _objc_add_category_flush_caches(theClass, cat, version);
}
// If the category's class exists but is unconnected,
// then attach the category to the class but don't bother
// flushing any method caches (because they must be empty).
// YES unconnected, NO class_handler
if ((theClass = look_up_class(cat->class_name, YES, NO))) {
_objc_add_category(theClass, cat, version);
return NO;
}
如果找到了,就載入category到對應的類,如果沒找到,就建立一個hash表,將這個category存起來,待之後系統去載入。
category的載入通過_objc_add_category_flush_caches函式實現:
/***********************************************************************
* _objc_add_category_flush_caches. Install the specified category's
* methods into the class it augments, and flush the class' method cache.
* Return YES if some method caches now need to be flushed.
**********************************************************************/
static bool _objc_add_category_flush_caches(Class cls, old_category *category, int version)
{
bool needFlush = NO;
// Install the category's methods into its intended class
{
mutex_locker_t lock(methodListLock);
_objc_add_category (cls, category, version);
}
// Queue for cache flushing so category's methods can get called
if (category->instance_methods) {
cls->setInfo(CLS_FLUSH_CACHE);
needFlush = YES;
}
if (category->class_methods) {
cls->ISA()->setInfo(CLS_FLUSH_CACHE);
needFlush = YES;
}
return needFlush;
}
這裡面主要就是呼叫_objc_add_category函式,該函式的實現如下:
/***********************************************************************
* _objc_add_category. Install the specified category's methods and
* protocols into the class it augments.
* The class is assumed not to be in use yet: no locks are taken and
* no method caches are flushed.
**********************************************************************/
static inline void _objc_add_category(Class cls, old_category *category, int version)
{
if (PrintConnecting) {
_objc_inform("CONNECT: attaching category '%s (%s)'", cls->name, category->category_name);
}
// Augment instance methods
if (category->instance_methods)
_objc_insertMethods (cls, category->instance_methods, category);
// Augment class methods
if (category->class_methods)
_objc_insertMethods (cls->ISA(), category->class_methods, category);
// Augment protocols
if ((version >= 5) && category->protocols)
{
if (cls->ISA()->version >= 5)
{
category->protocols->next = cls->protocols;
cls->protocols = category->protocols;
cls->ISA()->protocols = category->protocols;
}
else
{
_objc_inform ("unable to add protocols from category %s...\n", category->category_name);
_objc_inform ("class `%s' must be recompiled\n", category->class_name);
}
}
// Augment instance properties
if (version >= 7 && category->instance_properties) {
if (cls->ISA()->version >= 6) {
_class_addProperties(cls, category->instance_properties);
} else {
_objc_inform ("unable to add instance properties from category %s...\n", category->category_name);
_objc_inform ("class `%s' must be recompiled\n", category->class_name);
}
}
// Augment class properties
if (version >= 7 && category->hasClassPropertiesField() &&
category->class_properties)
{
if (cls->ISA()->version >= 6) {
_class_addProperties(cls->ISA(), category->class_properties);
} else {
_objc_inform ("unable to add class properties from category %s...\n", category->category_name);
_objc_inform ("class `%s' must be recompiled\n", category->class_name);
}
}
}
這裡主要是做了三件事:
1、將category的例項方法和類方法新增到類中
2、將category的protocol新增到類中
3、將category的例項物件和類物件新增到類中
至此,runtime幫助我們完成了所有的category的載入。
最後附上category的結構,其實也十分簡單:
struct old_category {
char *category_name;
char *class_name;
struct old_method_list *instance_methods;
struct old_method_list *class_methods;
struct old_protocol_list *protocols;
// Fields below this point are in version 7 or later only.
uint32_t size;
struct old_property_list *instance_properties;
// Check size for fields below this point.
struct old_property_list *class_properties;
bool hasClassPropertiesField() const {
return size >= offsetof(old_category, class_properties) + sizeof(class_properties);
}
};