本文扩展自: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); }};新闻热点
疑难解答