一、背景介绍

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

  

待续。。。。

Method Swizzling 剖析的更多相关文章

  1. runtime 第四部分method swizzling

    接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http:/ ...

  2. Objective-C Runtime 运行时之四:Method Swizzling

    理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Me ...

  3. 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入

    概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...

  4. Method Swizzling

    学习博客: http://www.cocoachina.com/ios/20160121/15076.html (这个作者太牛了,写了我一直想知道的类簇的swizz方法) 一. 一般的swizz 先给 ...

  5. Objective-C 利用OC的消息机制,使用Method Swizzling进行方法修改

    功能:修改父类不可修改函数方法,函数方法交换 应用场景:假如我们使用的他人提供一个的framework,.m已被打包成二进制.a无法修改源码,只留下.h头文件,那假如代码中某个函数出现了问题可以通过这 ...

  6. Method Swizzling (方法调配)

    Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每 ...

  7. iOS中AOP与Method Swizzling 项目中的应用

    引子:项目中需要对按钮点击事件进行统计分析,现在项目中就是在按钮的响应代码中添加点击事件,非常繁琐.所以使用了AOP(面向切面编程),将统计的业务逻辑统一抽离出来. 项目中添加的开源库:https:/ ...

  8. Method Swizzling和AOP(面向切面编程)实践

    Method Swizzling和AOP(面向切面编程)实践 参考: http://www.cocoachina.com/ios/20150120/10959.html 上一篇介绍了 Objectiv ...

  9. iOS运行时与method swizzling

    C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序如何运行的.而Objective-C是动态语言,它并非通过调用类的方 法来执行功能,而是给对象发送消息,对象在接收到消息之后 ...

随机推荐

  1. CSDN日报20170411 ——《怎样给自己的私活项目标价》

    [程序人生]怎样给自己的私活项目标价 作者:瞬息之间 非常早之前讲过我们"怎么接私活的心得技巧".相信非常多同学听了心里痒痒的.据我认识的(无论是现实生活还是网上接触的)朋友来看. ...

  2. windows系统添加服务命令

    管理员身份进入cmd sc create TestSvr binPath= D:\Program Files\test.exe start= auto

  3. ios浅谈关于nil和 NIL区别及相关问题

    本文转载至:http://blog.csdn.net/guozh/article/details/8469131 1.nil和null从字面意思来理解比较简单,nil是一个对象,而NULL是一个值,我 ...

  4. Echarts中axislabel文字过长导致显示不全或重叠

    最近在使用Echarts的时候,遇到点问题就是xAxis文字过长导致x轴的文字显示不全. 解决方案如下: 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HT ...

  5. 一个网络设备的常见功能--连通性检查SSRF漏洞--被黑客利用当做扫描器

    一.我们先来看一下很多网络设备都有的一个常见功能--连通性测试: 很多网络设备都具备与其他设备通信,联动的功能,例如网络设备联动安全设备,网络设备联动认证设备等等.此时都需要一个对端IP和对端端口号作 ...

  6. c#基础 第四讲

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  7. git学习(6)多人协作

    git学习(6)多人协作 当我们从远程仓库克隆的时候,git会自动的把本地的master和远程的master对应起来,并且远程仓库的默认名称是origin 查看远程库的信息 $ git remote ...

  8. 21.SQL to MongoDB Mapping Chart-官方文档摘录

    有关关系型数据库跟Mongod的语法对比 In addition to the charts that follow, you might want to consider the Frequentl ...

  9. HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'NTLM'。

    情况:WCF服务在浏览器中可以正常浏览,但是通过程序调用提示: HTTP request is unauthorized with client authentication scheme 'Anon ...

  10. http://echarts.baidu.com/demo.html#effectScatter-map

    http://echarts.baidu.com/demo.html#effectScatter-map