extension(类扩展)和 category(类别)
extension(类扩展)
简单来说,extension在.m文件中添加,所以其权限为private,所以只能拿到源码的类添加extension。另外extension是编译时决议,和interface和implement里的代码融合在一块了一般。 category(类别) category能在不继承类的情况下给类动态添加方法。 1、创建category
关于@dynamic的特性及用法可参考: https://blog.csdn.net/qq_28446287/article/details/79094491 2、category的优缺点 可以将类的实现代码分散到多个不同的文件或框架中
创建对私有方法的前向引用 OC语法中,你不能对一个类的方法定义为private,只有+、-,对实例变量可以private、public。 具体可参考此文档http://www.cnblogs.com/stevenwuzheng/p/5457487.html 向对象添加非正式协议 对NSObject进行一个类别叫做非正式协议,可以只实现想要的方法 3、category在runtime中的源码 typedef struct objc_category *Category; 1 struct objc_category { //类别的名字 char * _Nonnull category_name OBJC2_UNAVAILABLE; //该类的名字 char * _Nonnull class_name OBJC2_UNAVAILABLE; //实例方法列表 struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE; //类方法列表 struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE; //协议列表 struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; 明显看出,Category没有容纳变量的地方。 4、category的原理 objective-c的运行依赖runtime,runtime依赖于dyld动态加载,查看objc-os.mm文件发现其调用栈如下: 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(); static_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_2_images); dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images); } category被附加到类上是在map_images的时候发生的。在new-ABI的标准下,_objc_init函数里调用的map_iamges最终会调用objc-runtime-new.mm中的_read_images函数。_read_images中部分代码: // Discover categories. for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count); 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 /* || 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); } } } } ts.log("IMAGE TIMES: discover categories"); 从代码可以看出: 将category和它的主类(或元类)注册到哈希表中. 如果主类(或元类)已经实现,那么重建它的方列表。 category中的实例方法和属性被整合到主类中,而类方法被整合到元类中。而协议被同时整合到了主类和元类中。 /*********************************************************************** * remethodizeClass * Attach outstanding categories to an existing class. * Fixes up cls's method list, protocol list, and property list. * Updates method caches for cls and its subclasses. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void remethodizeClass(Class cls) { category_list *cats; bool isMeta; runtimeLock.assertWriting(); isMeta = cls->isMetaClass(); // Re-methodizing: check for more categories if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) { if (PrintConnecting) { _objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : ""); } attachCategories(cls, cats, true /*flush caches*/); free(cats); } } 如注释所说,此函数将未处理的category整合到主类(或元类),整合cls的方法、协议、属性列表,更新cls及其子类的方法缓存。 查看其中的attachCategories函数: // 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 void attachCategories(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_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 first int 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); 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); } 通过while循环,遍历所有的category,得每个category的方法列表mlist、proplist和protolist并存入主类(或元类)的mlists、proplists和protolists中。从而更新类的数据字段data()中mlist、proplist和protolist的值。 category没有替换掉原来类的方法,也就是说如果category和原来类有method1,那么在将category整合到原来类之后,类的method_list会有两个method1 category中的method1会被放在类的method_list前面,而原来类的method1被放 到了method_list后面,在调用时候会先调用method_list前面的,所以看起来是将原来类的method1覆盖了,实际上并不是那么回事。 5、category的两个面试题 3.1 一个类和它的category同时拥有一个方法,在调用时候调用哪一个?答案参考“2、category的原理” 3.2 一个类有多个category并都拥有同一个方法,在调用时候调用哪一个?答案参考“2、category的原理” 举个例子: //#import "type.m" - (void)test { NSLog(@"type class!!"); } //#import "type+xxxx.m" - (void)test { NSLog(@"category xxxx"); } //#import "type+xxxx1.m" - (void)test { NSLog(@"category xxxx1"); } //#import "type+xxxx2.m" - (void)test { NSLog(@"category xxxx2"); }
可以知道,输出的结果跟compile source文件中的category的.m文件放置顺序有关。且放最底部的时候输出(主类.m文件的放置不影响,理由参考”2、category的原理”) 6、category动态添加变量 @interface type (xxxx) @property (nonatomic, assign) NSInteger number; @end static void *numberKey = &numberKey; @implementation type (xxxx) - (void)setNumber:(NSInteger)number { objc_setAssociatedObject(self, &numberKey, [NSString stringWithFormat:@"%ld",number], OBJC_ASSOCIATION_ASSIGN); } - (NSInteger)number { return [objc_getAssociatedObject(self, &numberKey) integerValue]; } @end objc_setAssociatedObject和objc_getAssociatedObject的描述可参考:https://www.cnblogs.com/liuting-1204/p/6526342.html