1. 分类的用处
- 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,a)可以减少单个文件的体积 b)可以把不同的功能组织到不同的category里 c)可以由多个开发者共同完成一个类 d)可以按需加载想要的category 等等。
- 声明私有方法
- 模拟多继承
- 把framework的私有方法公开
2.分类和扩展
扩展看起来像一个匿名的分类,但是extension和有名字的category几乎完全是两个东西。
扩展:编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
分类:运行期决议的。category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
3.分类的实现原理
1 typedef struct category_t { 2 const char *name; 3 classref_t cls; 4 struct method_list_t *instanceMethods; 5 struct method_list_t *classMethods; 6 struct protocol_list_t *protocols; 7 struct property_list_t *instanceProperties; 8 } category_t;
从category的定义也可以看出category的使用(可以添加实例方法,类方法,甚至可以实现协议,添加属性)。
调用顺序:
void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? environ_init(); tls_init(); lock_init(); exception_init(); // Register for unmap first, in case some +load unmaps something _dyld_register_func_for_remove_image(&unmap_image); dyld_register_image_state_change_handler(dyld_image_state_bound, 1/*batch*/, &map_images); dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
1.libsystem 调用_objc_init
2.在_objc_init中,objc-runtime-new.mm 调用 map_images ,最后_read_images 。
3.load所以的类 协议 分类 调用 +load方法。
// Discover categories. for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; class_t *cls = remapClass(cat->cls); if (!cls) { // Category‘s target class is missing (probably weak-linked). // Disavow any knowledge of this category. catlist[i] = NULL; 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 (isRealized(cls)) { remethodizeClass(cls); classExists = YES; } } if (cat->classMethods || cat->protocols /* || cat->classProperties */) { addUnattachedCategoryForClass(cat, cls->isa, hi); if (isRealized(cls->isa)) { remethodizeClass(cls->isa); } } } }
1)、把category的实例方法、协议以及属性添加到类上
2)、把category的类方法和协议添加到类的metaclass上
注意:
1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。
3)、添加category到类上的工作会先于+load方法,所以调用类的方法时(分类也实现了相同的方法),会先调用分类的方法。如果多个分类,会按编译顺序决定。
4)、+load方法 的执行顺序是先类,后category。如果类和分类同时实现+load方法,会先调用类的+load。 如果是多个分类,一样会按编译顺序决定。
4.小技巧
怎么调用到原来类中被category覆盖掉的方法?
Class currentClass = [MyClass class]; MyClass *my = [[MyClass alloc] init]; if (currentClass) { unsigned int methodCount; Method *methodList = class_copyMethodList(currentClass, &methodCount); IMP lastImp = NULL; SEL lastSel = NULL; for (NSInteger i = 0; i < methodCount; i++) { Method method = methodList[i]; NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) encoding:NSUTF8StringEncoding]; if ([@"printName" isEqualToString:methodName]) { lastImp = method_getImplementation(method); lastSel = method_getName(method); } } typedef void (*fn)(id,SEL); if (lastImp != NULL) { fn f = (fn)lastImp; f(my,lastSel); } free(methodList);
我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法。