通常通过method swizzle可以交换两个方法的实现(不限于同一个类型),先看一段代码:
People类
1 2 3 4
| - (void)talk { NSLog(@"%@", self.class); }
|
Student类继承People
Student
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SEL originalSelector = NSSelectorFromString(@"talk"); SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk"); Method originalMethod = class_getInstanceMethod(self.class, originalSelector); Method swizzleMethod = class_getInstanceMethod(self.class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); }); } - (void)swizzle_talk { NSLog(@"swizzle_talk: %@", self.class); }
|
Method在objc-private.h中有如下定义:
1
| typedef struct old_method *Method;
|
old_method是结构体,它定义在objc-runtime-old.h中:
1 2 3 4 5
| struct old_method { SEL method_name; char *method_types; IMP method_imp; }
|
Method中包含了3个部分,第一部分是函数名,通常可以通过@selector()获取,第二部分是函数声明, 第三部分是函数实现,理解成函数指针。
class_getInstanceMethod有两个参数,第一个参数是class,第二个参数是selector。这个函数是以class开头的,第一个参数也是传的class对象,所以可以理解为从所传递的类对象中查找指定的数据,类对象可以通过实例对象的class方法活的,类对象全局只有一个。
Class对象的定义如下:
1
| typedef struct objc_class *Class;
|
也就是说Class对象其实是objc_class结构体,平时使用的self.class得到的是一个objc_class的结构体指针。
objc_class定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| struct objc_class : objc_object { Class superclass; const char *name; uint32_t version; uint32_t info; uint32_t instance_size; struct old_ivar_list *ivars; struct old_method_list **methodLists; Cache cache; struct old_protocol_list *protocols; // CLS_EXT only const uint8_t *ivar_layout; struct old_class_ext *ext; }
|
这里只列出了字段,函数并没有列出。可以看到一个类对象里面包含了以下比较重要的信息:
1.它的基类对象字段superclass
2.它的实例对象有哪些字段 ivars
3.它的实例对象有哪些方法,存储在方法列表中 **methodLists, 这里为什么是指针的指针,就是它可能包含多个方法列表。
4.它属于什么类型的类对象:info,比如CLS_CLASS还是CLS_META,相当于类对象自己的元数据信息。通过它可以判断出一个类对象是否是元类对象。
以下是class_getInstanceMethod的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| Method class_getInstanceMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; // This deliberately avoids +initialize because it historically did so. // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. Method meth; meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache); if (meth == (Method)1) { // Cache contains forward:: . Stop searching. return nil; } else if (meth) { return meth; } // Search method lists, try method resolver, etc. lookUpImpOrNil(cls, sel, nil, NO/*initialize*/, NO/*cache*/, YES/*resolver*/); meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache); if (meth == (Method)1) { // Cache contains forward:: . Stop searching. return nil; } else if (meth) { return meth; } return _class_getMethod(cls, sel); }
|
这一部分主要是先从方法缓存里取方法,主要看下_class_getMethod
1 2 3 4 5 6
| static Method _class_getMethod(Class cls, SEL sel) { mutex_locker_t lock(methodListLock); return (Method)_getMethod(cls, sel); }
|
在_class_getMethod中调用了_getMethod函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static inline old_method * _getMethod(Class cls, SEL sel) { for (; cls; cls = cls->superclass) { old_method *m; m = _findMethodInClass(cls, sel); if (m) return m; } return nil; }
|
_getMethod是主要的实现了,这里通过_findMethodInClass函数来查找类对象的方法,并且便利了父类对象。也就是说,基类中的方法也会被遍历到。
继续再看下_findMethodInClass函数的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| static inline old_method * _findMethodInClass(Class cls, SEL sel) { // Flattened version of nextMethodList(). The optimizer doesn't // do a good job with hoisting the conditionals out of the loop. // Conceptually, this looks like: // while ((mlist = nextMethodList(cls, &iterator))) { // old_method *m = _findMethodInList(mlist, sel); // if (m) return m; // } if (!cls->methodLists) { // No method lists. return nil; } else if (cls->info & CLS_NO_METHOD_ARRAY) { // One method list. old_method_list **mlistp; mlistp = (old_method_list **)&cls->methodLists; *mlistp = fixupSelectorsInMethodList(cls, *mlistp); return _findMethodInList(*mlistp, sel); } else { // Multiple method lists. old_method_list **mlistp; for (mlistp = cls->methodLists; *mlistp != nil && *mlistp != END_OF_METHODS_LIST; mlistp++) { old_method *m; *mlistp = fixupSelectorsInMethodList(cls, *mlistp); m = _findMethodInList(*mlistp, sel); if (m) return m; } return nil; } } static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) { int i; if (!mlist) return nil; for (i = 0; i < mlist->method_count; i++) { old_method *m = &mlist->method_list[i]; if (m->method_name == sel) { return m; } } return nil; }
|
这个方法主要是通过遍历类对象的方法列表字段,来查找某个方法。
在_findMethodInList函数中,它其实是比较了方法列表中方法的Selector和要找的Selector是不是同一个来查找这个方法。所以通过selector就可以定位到一个method,也就是可以得到它的IMP和Type了。
所以可以很好理解一下2个方法:
method_getTypeEncoding
method_getImplementation
通过以上分析,可以知道class_getInstanceMethod是获得某个类对象中的方法对象,这个过程中会遍历到父类中。也就是当前类没有实现的方法,父类来抵,也符合面向对象的设计。
总的说来,class_getxxxxxxx是通过查找类对象内部数据来得到一些消息,类似的还有
class_getClassMethod,它是获取类方法的函数:
看看它的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; return class_getInstanceMethod(cls->getMeta(), sel); } Class getMeta() { if (isMetaClass()) return (Class)this; else return this->ISA(); } bool isMetaClass() { return info & CLS_META; } #define CLS_CLASS 0X1 #define CLS_META 0x2
|
可以知道如果当前类就是元类对象,就返回它自己反之返回this→ISA();
objc_class继承自objc_object,函数ISA是objc_object中定义的:
1 2 3 4 5 6 7 8 9 10 11
| truct objc_object { private: isa_t isa; } uion isa_t { Class clas; }
|
相当于取出objc_class对象的cls信息,也就是元类对象了。
然后通过cls_getInstanceMethod来去到Method信息,跟之前取类对象中的Method一样,只是多了一步取元类对象的步骤。
在理解了class_getInstanceMethod函数之后,再来看一下class_addMethod函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { IMP old; if (!cls) return NO; old = _class_addMethod(cls, name, imp, types, NO); return !old; } static IMP _class_addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) { old_method *m; IMP result = nil; if (!types) types = ""; mutex_locker_t lock(methodListLock); if ((m = _findMethodInClass(cls, name))) { // already exists // fixme atomic result = method_getImplementation((Method)m); if (replace) { method_setImplementation((Method)m, imp); } } else { // fixme could be faster old_method_list *mlist = (old_method_list *)calloc(sizeof(old_method_list), 1); mlist->obsolete = fixed_up_method_list; mlist->method_count = 1; mlist->method_list[0].method_name = name; mlist->method_list[0].method_types = strdup(types); mlist->method_list[0].method_imp = imp; _objc_insertMethods(cls, mlist, nil); if (!(cls->info & CLS_CONSTRUCTING)) { flush_caches(cls, NO); } else { // in-construction class has no subclasses flush_cache(cls); } result = nil; } return result; }
|
相当于当前类对象中存在这个方法的时候(包括父类的),什么都不会处理返回NO。如果不存在那么会添加一个,并且返回YES。
接着是class_replaceMethod
1 2 3 4 5
| IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return nil; return _class_addMethod(cls, name, imp, types, YES); }
|
该方法和class_addMethod的区别是,如果发现已经存在sel对应的Method,前者会直接通过新的imp覆盖原来的method,后者则不会做任何处理。
最后method_exchangeImplementations交换两个method的实现。
现在分析一下文章开头那段代码,当当前类本身没有实现original_selector方法的时候,但是它的基类实现了。那么最后交换的就是基类中的original_selector方法,这将会影响基类和其他继承子类的行为。现在通过一个简单的demo来验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| @interface People : NSObject - (void)talk; @end @implementation People - (void)talk { NSLog(@"%@", self.class); } @interface Student : People @end @implemention Student @end @interface Teacher : People @end @implemention Teacher @end @interface Student (Tracking) @end @implemention Student + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SEL originalSelector = NSSelectorFromString(@"talk"); SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk"); Method originalMethod = class_getInstanceMethod(self.class, originalSelector); Method swizzleMethod = class_getInstanceMethod(self.class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); }); } - (void)swizzle_talk { NSLog(@"zwizzle_talk: %@", self.class); } @end - (void)viewDidLoad { [super viewDidLoad]; Teacher *t = [[Teacher alloc] init]; [t talk]; Student *stu = [[Student alloc] init]; [stu talk]; } @end
|
输出是:
1 2 3
| 20:15:35.432 abc[87901:2148310] zwizzle_talk: Teacher 20:15:35.433 abc[87901:2148310] zwizzle_talk: Student
|
说明 Teacher类也收到了student swizzle的影响。
Student(Tracking)换一种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SEL originalSelector = NSSelectorFromString(@"talk"); SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk"); Method originalMethod = class_getInstanceMethod(self.class, originalSelector); Method swizzleMethod = class_getInstanceMethod(self.class, swizzleSelector); BOOL addMethod = class_addMethod(self.class, originalSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding( swizzleMethod)); if (addMethod) { class_replaceMethod(self.class, swizzleSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzleMethod); } }); }
|
输出是:
1 2 3
| 20:19:50.683 abc[87966:2152486] Teacher 20:19:50.684 abc[87966:2152486] zwizzle_talk: Student
|
可以看到,Teacher类并没有收到影响,虽然是基类中实现了talk方法,但是通过class_addMethod给当前类Student动态增加了talk的实现,然后进行交换。没有影响到原来People类中的talk方法。
可以看出,第二种方法实现起来更好,影响范围更小一些。