本文擴展自:http://blog.sunnyxx.com/2014/08/30/objc-PRe-main/ 這篇文章主要描述了iOS平臺上main函數調用之前所發生的事。我們從這里開始講述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); }};新聞熱點
疑難解答