一、背景介绍
关于Method Swizzling的文章一大堆,讲的非常好的也数不胜数。不过,很多人只是会用,知道一些注意点。深入一点问的话,估计就答得不好。归其原因就是对Method Swizzling 理解的不够透彻。本文些的初衷就是为了让大家更容易理解,仅此而已。如若有错之处,还望指正。
二、经典代码
SEL originalSelector = @selector(applicationDidBecomeActive:); SEL swizzledSelector = @selector(my_applicationDidBecomeActive:); Method originalMethod = class_getInstanceMethod([self class], originalSelector); Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector); BOOL didAddMethod = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }
上面这段代码是相对较完善的一段,也有同学会写成下面这种:
SEL originalSelector = @selector(applicationDidBecomeActive:); SEL swizzledSelector = @selector(my_applicationDidBecomeActive:); Method originalMethod = class_getInstanceMethod([self class], originalSelector); Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod);
大部分人也是懂原因的:如果Swizzling的 方法是父类里的,而子类里面没有重写,第二种写法就有可能不妥。因为按照第二种写法,会把父类的方法Swizzling,这样会导致所有的子类都会受影响,这个可能不是你想要的。
三、解释一下细节
大部分人只是知道怎么用,用的时候copy一下,但是要有刨根问底的精神,知其然,知其所以然。
SEL :类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。在内存中每个类的方法都存储在类对象中,每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据就可以找到对应的方法地址,进而调用方法。
SEL类型的定义: typedef struct objc_selector *SEL
IMP:Implement缩写,表示指向方法的实现地址,可通过IMP来调用方法。
Method:An opaque type that represents a method in a class definition。
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE;
我们看一下类里面是怎样存放Method的:
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists 这个就是类存放Method的数组。
可以看到 Class 、SEL、Method等 都是结构体,IMP是函数 的指针地址。
@selector(applicationDidBecomeActive:) 获取applicationDidBecomeActive:方法的 SEL。
class_getInstanceMethod([self class], originalSelector); 获取applicationDidBecomeActive:方法的 Method。
以上都是一些简单概念,接下来我们讲下重点,也就是几个系统API。
class_getInstanceMethod源码:
/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/ Method class_getInstanceMethod(Class cls, SEL sel) {
//先判读 传入的cls 和 sel是否为nil,如果nil 直接return nil了 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. // 先从方法缓存列表里面寻找 ,找到return ,找不到继续 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; } //根据下面再次从缓存寻找,也知道lookUpImpOrNil的作用就是从 objc_method_list寻找,但并没有直接return,而是找完丢入了方法缓存列表里了 // 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); }
我们来看下lookUpImpOrNil 具体干了什么:
/*********************************************************************** * lookUpImpOrNil. * Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache **********************************************************************/ IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); if (imp == _objc_msgForward_impcache) return nil; else return imp; }
里面搞了个传送门lookUpImpOrForward,我们看看lookUpImpOrForward做了什么:
/*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { Class curClass; IMP methodPC = nil; Method meth; bool triedResolver = NO; methodListLock.assertUnlocked(); // Optimistic cache lookup if (cache) { methodPC = _cache_getImp(cls, sel); if (methodPC) return methodPC; } // Check for freed class if (cls == _class_getFreedObjectClass()) return (IMP) _freedHandler; // Check for +initialize if (initialize && !cls->isInitialized()) { _class_initialize (_class_getNonMetaClass(cls, inst)); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } // The lock is held to make method-lookup + cache-fill atomic // with respect to method addition. Otherwise, a category could // be added but ignored indefinitely because the cache was re-filled // with the old value after the cache flush on behalf of the category. retry: methodListLock.lock(); // Try this class's cache. methodPC = _cache_getImp(cls, sel); if (methodPC) goto done; // Try this class's method lists. meth = _class_getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, cls, meth, sel); methodPC = method_getImplementation(meth); goto done; } // Try superclass caches and method lists. curClass = cls; while ((curClass = curClass->superclass)) { // Superclass cache. meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache); if (meth) { if (meth != (Method)1) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, curClass, meth, sel); methodPC = method_getImplementation(meth); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. meth = _class_getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, curClass, meth, sel); methodPC = method_getImplementation(meth); goto done; } } // No implementation found. Try method resolver once. if (resolver && !triedResolver) { methodListLock.unlock(); _class_resolveMethod(cls, sel, inst); triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help. // Use forwarding. _cache_addForwardEntry(cls, sel); methodPC = _objc_msgForward_impcache; done: methodListLock.unlock(); return methodPC; }
代码有点长,我不想一一解释了,简单描述一下:
1.就是先从缓存列表里面寻找Method,找到就return;
2.找不到就从类的struct objc_method_list **methodLists里面寻找,找到return;
3.如果还是找不到,就去父类 缓存找,找不到就去父类 方法列表 找;
4.如果还是找不到,再去父类的父类的 缓存找。。。 while 这一块相当于一个递归吧,一层一层往上撸。
5.代码里面有log_and_fill_cache 或是_cache_addForwardEntry ,他们作用就是将 找到的Method 加到缓存里。
6.另外我们看了锁 和 强大的 goto,goto是 C 里面强大的 “时光传送门”,菜鸟慎用 :) 大神也有坠机时候。
感觉有点写不下去了,捂着眼睛继续吧,来~,我们看一下log_and_fill_cache的实现:
/*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/ static void log_and_fill_cache(Class cls, Class implementer, Method meth, SEL sel) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif _cache_fill (cls, meth, sel); }
我不管了,继续_cache_fill:
/*********************************************************************** * _cache_fill. Add the specified method to the specified class' cache. * Returns NO if the cache entry wasn't added: cache was busy, * class is still being initialized, new entry is a duplicate. * * Called only from _class_lookupMethodAndLoadCache and * class_respondsToMethod and _cache_addForwardEntry. * * Cache locks: cacheUpdateLock must not be held. **********************************************************************/ bool _cache_fill(Class cls, Method smt, SEL sel) { uintptr_t newOccupied; uintptr_t index; cache_entry **buckets; cache_entry *entry; Cache cache; cacheUpdateLock.assertUnlocked(); // Never cache before +initialize is done if (!cls->isInitialized()) { return NO; } // Keep tally of cache additions totalCacheFills += 1; mutex_locker_t lock(cacheUpdateLock); entry = (cache_entry *)smt; cache = cls->cache; // Make sure the entry wasn't added to the cache by some other thread // before we grabbed the cacheUpdateLock. // Don't use _cache_getMethod() because _cache_getMethod() doesn't // return forward:: entries. if (_cache_getImp(cls, sel)) { return NO; // entry is already cached, didn't add new one } // Use the cache as-is if it is less than 3/4 full newOccupied = cache->occupied + 1; if ((newOccupied * 4) <= (cache->mask + 1) * 3) { // Cache is less than 3/4 full. cache->occupied = (unsigned int)newOccupied; } else { // Cache is too full. Expand it. cache = _cache_expand (cls); // Account for the addition cache->occupied += 1; } // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. buckets = (cache_entry **)cache->buckets; for (index = CACHE_HASH(sel, cache->mask); buckets[index] != NULL; index = (index+1) & cache->mask) { // empty } buckets[index] = entry; return YES; // successfully added new cache entry }
上面就是将Method加到cache里的,细节我就不说了,不过有一点我们还是要说下: Use the cache as-is if it is less than 3/4 full,是的,针对缓存,苹果是有个机制的,当缓存达到3/4时候会释放的,重新来,是不是想起了NSCache?
_cache_addForwardEntry这个方法:
/*********************************************************************** * _cache_addForwardEntry * Add a forward:: entry for the given selector to cls's method cache. * Does nothing if the cache addition fails for any reason. * Called from class_respondsToMethod and _class_lookupMethodAndLoadCache. * Cache locks: cacheUpdateLock must not be held. **********************************************************************/ void _cache_addForwardEntry(Class cls, SEL sel) { cache_entry *smt; smt = (cache_entry *)malloc(sizeof(cache_entry)); smt->name = sel; smt->imp = _objc_msgForward_impcache; if (! _cache_fill(cls, (Method)smt, sel)) { // fixme hack // Entry not added to cache. Don't leak the method struct. free(smt); } }
一样的,也是调用了_cache_fill,嗯!关于class_getInstanceMethod就聊到这里,打住!
OBJC_EXPORT IMP method_getImplementation(Method m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
知道Method是个结构体以及其结构,就能知道这个方法实现其实很简单:
IMP method_getImplementation(Method m) { if (!m) return nil; return oldmethod(m)->method_imp; }
//就这么简单
我们看看method_exchangeImplementations的实现:
void method_exchangeImplementations(Method m1, Method m2) { if (!m1 || !m2) return; rwlock_writer_t lock(runtimeLock); // 这不是 a b容器里的内容交换么,找到了第三个容器c来帮忙,是不是源码也是很简单的,瞄一眼就知道了。 IMP m1_imp = m1->imp; m1->imp = m2->imp; m2->imp = m1_imp; // RR/AWZ updates are slow because class is unknown // Cache updates are slow because class is unknown // fixme build list of classes whose Methods are known externally? flushCaches(nil); updateCustomRR_AWZ(nil, m1); updateCustomRR_AWZ(nil, m2); }
交换两个Method 结构体里面的IMP而已。
class_addMethod 我们再来看一下这个函数:
/*********************************************************************** * class_addMethod **********************************************************************/ 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; }
/*********************************************************************** * class_addMethod **********************************************************************/ 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);//加锁
// 先判断该方法是否存在这个类里,如果存在,只改下Method 的IMP就可以 if ((m = _findMethodInClass(cls, name))) { // already exists // fixme atomic result = method_getImplementation((Method)m); if (replace) { method_setImplementation((Method)m, imp); } } else {
// 如果不存在这个类里,则要动态为这个class 插入一个方法 // 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; }
官方wiki:
待续。。。。