一、背景介绍

关于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;
}

  

待续。。。。

05-11 21:52