- Category的使用场合是什么?
category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景
- 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,
a)可以减少单个文件的体积
b)可以把不同的功能组织到不同的category里
c)可以由多个开发者共同完成一个类
d)可以按需加载想要的category 等等。
- 声明私有方法
不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景:
模拟多继承
把framework的私有方法公开
Objective-C的这个语言特性对于纯动态语言来说可能不算什么,比如javascript,你可以随时为一个“类”或者对象添加任意方法和实例变量。但是对于不是那么“动态”的语言而言,这确实是一个了不起的特性。
- Category的实现原理
//分类的内存结构,多个分类会对应的多个category_t。struct category_t {const char *name;classref_t cls;struct method_list_t *instanceMethods; //对象方法struct method_list_t *classMethods; //类方法struct protocol_list_t *protocols; //协议信息struct property_list_t *instanceProperties; //属性// Fields below this point are not always present on disk.struct property_list_t *_classProperties;method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);};
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
- Category的加载处理过程
/************************************************************************ _read_images* Perform initial processing of the headers in the linked* list beginning with headerList.** Called by: map_images_nolock** Locking: runtimeLock acquired by map_images**********************************************************************/void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){......// add by chenjie// Discover categories.for (EACH_HEADER) {//获取系统的category列表category_t **catlist =_getObjc2CategoryList(hi, &count);bool hasClassProperties = hi->info()->hasCategoryClassProperties();for (i = 0; i < count; i++) {category_t *cat = catlist[i];Class cls = remapClass(cat->cls);if (!cls) {// Category's target class is missing (probably weak-linked).// Disavow any knowledge of this category.catlist[i] = nil;if (PrintConnecting) {_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with ""missing weak-linked target class",cat->name, cat);}continue;}// Process this category.// First, register the category with its target class.// Then, rebuild the class's method lists (etc) if// the class is realized.bool classExists = NO;if (cat->instanceMethods || cat->protocols|| cat->instanceProperties){addUnattachedCategoryForClass(cat, cls, hi);if (cls->isRealized()) {//重新组织了类对象的方法。remethodizeClass(cls);classExists = YES;}if (PrintConnecting) {_objc_inform("CLASS: found category -%s(%s) %s",cls->nameForLogging(), cat->name,classExists ? "on existing class" : "");}}if (cat->classMethods || cat->protocols|| (hasClassProperties && cat->_classProperties)){addUnattachedCategoryForClass(cat, cls->ISA(), hi);if (cls->ISA()->isRealized()) {//重新组织了元类对象的方法。remethodizeClass(cls->ISA());}if (PrintConnecting) {_objc_inform("CLASS: found category +%s(%s)",cls->nameForLogging(), cat->name);}}}}}// Attach method lists and properties and protocols from categories to a class.// Assumes the categories in cats are all loaded and sorted by load order,// oldest categories first.static voidattachCategories(Class cls, category_list *cats, bool flush_caches){if (!cats) return;if (PrintReplacedMethods) printReplacements(cls, cats);bool isMeta = cls->isMetaClass();// fixme rearrange to remove these intermediate allocations// 方法数组/*[[method_t, method_t],[method_t, method_t]]*/method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));//属性数组property_list_t **proplists = (property_list_t **)malloc(cats->count * sizeof(*proplists));//协议数组protocol_list_t **protolists = (protocol_list_t **)malloc(cats->count * sizeof(*protolists));// Count backwards through cats to get newest categories firstint mcount = 0;int propcount = 0;int protocount = 0;int i = cats->count;bool fromBundle = NO;while (i--) {//取出某得分类auto& entry = cats->list[i];//取出分类中的对象方法method_list_t *mlist = entry.cat->methodsForMeta(isMeta);if (mlist) {mlists[mcount++] = mlist;fromBundle |= entry.hi->isBundle();}property_list_t *proplist =entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) {proplists[propcount++] = proplist;}protocol_list_t *protolist = entry.cat->protocols;if (protolist) {protolists[protocount++] = protolist;}}//得到类对象里面的数组auto rw = cls->data();prepareMethodLists(cls, mlists, mcount, NO, fromBundle);// 将所有分类的对象方法,附加到类对象的方法列表中rw->methods.attachLists(mlists, mcount);free(mlists);if (flush_caches && mcount > 0) flushCaches(cls);// 将所有分类的属性,附加到类对象的属性列表中rw->properties.attachLists(proplists, propcount);free(proplists);// 将所有分类的协议,附加到类对象的协议列表中rw->protocols.attachLists(protolists, protocount);free(protolists);}void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) {uint32_t oldCount = array()->count;uint32_t newCount = oldCount + addedCount;//重新开辟内存空间setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));array()->count = newCount;//array()->lists 原来的方法列表memmove(array()->lists + addedCount, array()->lists,oldCount * sizeof(array()->lists[0]));//addedLists 新增的方法列表memcpy(array()->lists, addedLists,addedCount * sizeof(array()->lists[0]));}
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中后面参与编译的Category数据,会在数组的前面
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
- Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
- +load方法

//加载load方法的步骤void prepare_load_methods(const headerType *mhdr){size_t count, i;classref_t *classlist =_getObjc2NonlazyClassList(mhdr, &count);for (i = 0; i < count; i++) {schedule_class_load(remapClass(classlist[i]));}}static void schedule_class_load(Class cls){if (!cls) return;assert(cls->isRealized()); // _read_images should realizeif (cls->data()->flags & RW_LOADED) return;// Ensure superclass-first orderingschedule_class_load(cls->superclass);add_class_to_loadable_list(cls);cls->setInfo(RW_LOADED);}void add_class_to_loadable_list(Class cls){IMP method;loadMethodLock.assertLocked();method = cls->getLoadMethod();if (!method) return; // Don't bother if cls has no +load methodif (PrintLoading) {_objc_inform("LOAD: class '%s' scheduled for +load",cls->nameForLogging());}if (loadable_classes_used == loadable_classes_allocated) {loadable_classes_allocated = loadable_classes_allocated*2 + 16;loadable_classes = (struct loadable_class *)realloc(loadable_classes,loadable_classes_allocated *sizeof(struct loadable_class));}//loadable_classes是保存load方法的列表loadable_classes[loadable_classes_used].cls = cls;loadable_classes[loadable_classes_used].method = method;loadable_classes_used++;}//调用load方法void call_load_methods(void){static bool loading = NO;bool more_categories;loadMethodLock.assertLocked();// Re-entrant calls do nothing; the outermost call will finish the job.if (loading) return;loading = YES;void *pool = objc_autoreleasePoolPush();do {// 1. Repeatedly call class +loads until there aren't any morewhile (loadable_classes_used > 0) {call_class_loads();}// 2. Call category +loads ONCEmore_categories = call_category_loads();// 3. Run more +loads if there are classes OR more untried categories} while (loadable_classes_used > 0 || more_categories);objc_autoreleasePoolPop(pool);loading = NO;}static bool call_category_loads(void){int i, shift;bool new_categories_added = NO;// Detach current loadable list.struct loadable_category *cats = loadable_categories;int used = loadable_categories_used;int allocated = loadable_categories_allocated;loadable_categories = nil;loadable_categories_allocated = 0;loadable_categories_used = 0;// Call all +loads for the detached list.for (i = 0; i < used; i++) {Category cat = cats[i].cat;load_method_t load_method = (load_method_t)cats[i].method;Class cls;if (!cat) continue;cls = _category_getClass(cat);if (cls && cls->isLoadable()) {if (PrintLoading) {_objc_inform("LOAD: +[%s(%s) load]\n",cls->nameForLogging(),_category_getName(cat));}(*load_method)(cls, SEL_load);cats[i].cat = nil;}}}
应用场景
在执行load方法时,整个应用程序都会阻塞等着所有类的load方法都执行完才继续,因此尽力减少在load函数中所做的操作。这个类真正的用途一般只用于调试程序,比如可以在分类中实现此方法,用来判断该分类是否已经正确载入系统。此外,一般在需要method swizzling时,一般是在load函数中实现,可保证程序一经加载方法就交换完成,且只执行一次。
- +initialize方法

Method class_getInstanceMethod(Class cls, SEL sel){// Search method lists, try method resolver, etc.lookUpImpOrNil(cls, sel, nil,NO/*initialize*/, NO/*cache*/, YES/*resolver*/);return _class_getMethod(cls, sel);}/************************************************************************ class_initialize. Send the '+initialize' message on demand to any* uninitialized class. Force initialization of superclasses first.**********************************************************************/void _class_initialize(Class cls){assert(!cls->isMetaClass());Class supercls;bool reallyInitialize = NO;// Make sure super is done initializing BEFORE beginning to initialize cls.// See note about deadlock above.//先调用父类的初始化方法supercls = cls->superclass;if (supercls && !supercls->isInitialized()) {_class_initialize(supercls);}// Try to atomically set CLS_INITIALIZING.{monitor_locker_t lock(classInitLock);if (!cls->isInitialized() && !cls->isInitializing()) {cls->setInitializing();reallyInitialize = YES;}}}
- Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
- load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
2.initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
- Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
总结
load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。
