Method Swizzle

1、AOP编程思想

1.1、AOP是什么

AOP(Aspect Oriented Programming)直译为面向切面编程。假设把应用程序想象成一个长方体,OOP就是纵向划分系统,把系统分成很多个业务模块,如用户管理,产品展示等模块;AOP横向划分系统,提取各个业务模块可能要重复操作的部分。
AOP编程的原理是在不更改正常的业务处理流程的前提下,通过生成一个动态代理类,从而实现对目标对象嵌入附加的操作。AOP是OOP的一个补充。

1.2、AOP的使用场景

AOP最常见的使用场景:日志记录、性能统计、安全控制、事务处理、异常处理、调试等。这些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。

比如,最常见的用户习惯统计。最简单的思路是:在viewDidAppearviewDidDisappear两个方法中,分别调用对应的第三方接口来实现页面驻留时长统计。
但是这部分代码并不涉及页面逻辑,而且app中的统计事件代码会散布在各个viewcontroller中,你可能会忘记自己添加的统计事件,也会不利于统计相关的修改。
这时候,我们就会想把类似通用的代码从业务逻辑中分离出去,整合到一块。

2、Method Swizzling

2.1、Method Swizzling是什么

在iOS中实现AOP编程思想的一种方式是Method SwizzlingMethod Swizzling利用Runtime特性把一个方法的实现和另一个方法的实现进行替换,在程序运行时修改Dispatch TableSELIMP的映射关系。
通过 swizzling method 改变目标函数的 selector 所指向的实现,然后在新的实现中实现附加的操作,完成之后再调用原来的处理逻辑。

2.2、Method Swizzling的优势

  • 继承:修改多,无法保证他人一定继承基类
  • 类别:类别中重写方法会覆盖原来的实现,大多时候,重写一个方法是为了添加一些代码,而不是完全取代它。如果两个类别都实现了相同的方法,只有一个类别的方法会被调用到。
  • AOP优势:减少切面业务的重复编码,减少与其他业务的耦合,把琐碎事务从主业务中分离。

2.3、实现方案

  • swizzling method的实现原理这里不再说明,相关知识:Objective-C调用方法的原理(包括消息解析与转发)。
  • 最常见的:在+(void)load中自己实现swizzling method的代码。
  • Github 上有一个基于 swizzling method 的开源框架 Aspects:AspectsNSObject的Category,任何NSObject类要hook实例方法或者类方法,只需调用Aspects提供的一个简单方法,就可以实现hook,并且可以安排附加代码的执行时机。

3、常见的实现

3.1、普通类的swizzling

关键代码与注释:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(testMethod));
        Method swizzleMethod = class_getInstanceMethod([self class
                                                        ],@selector(swizzle_testMethod));
/**************第一种替换方式**********/
        // 如果当前类没有实现名为originalSelector的方法,originalMethod如果不为空,拿到的则是其父类的名为originalSelector的方法的IMP,这里先给当前类新增originalSelector的IMP,对应的IMP是swizzleMethod中的IMP
        BOOL didAddMethod = class_addMethod([self class], @selector(testMethod), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAddMethod) {// 如果当前类已经有名为originalSelector的实现,则返回NO
            // 将swizzleSelector的IMP替换成父类originalSelector的IMP。
            class_replaceMethod([self class], @selector(swizzle_testMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            // 如果当前类都有实现待替换的两个方法,直接交换
          //   这两个参数没有先后顺序
            method_exchangeImplementations(originalMethod, swizzleMethod);
        }
 /**************第二种替换方式,有问题,不要用**********/
        // class_replaceMethod这个方法的效果:
        // 如果在当前类有对应的实现,执行method_setImplementation
        // 如果当前类没有对应的实现,执行class_addMethod
        // 这儿的先后顺序是有讲究的,应该优先把新的方法指定原IMP,再修改原有的方法的IMP
        // 如果先执行原方法指向新的IMP,那么在执行完瞬间方法被调用容易引发死循环,这样的顺序至少保证如果没有hook成功,也只是调用原来的实现,不会出现死循环
/////error///////// 这段代码有问题,hook自己写的testMethod,无法输出附加的代码
/////error/////////  而且如果父类同时也写了一样的swizzle的代码,hook会被抵消
//        class_replaceMethod([self class], @selector(swizzle_testMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//        class_replaceMethod([self class], @selector(testMethod), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    });
}

3.2、类簇类的swizzling

在Objective-C中有一些类叫类簇,比如:NSNumberNSArrayNSMutableArray等。
何为类簇,类簇其实是抽象工厂模式的实现,比如NSArray并不是一个具体类,其实它是一个抽象类,它对外提供了统一访问的接口,它的真实的类型会根据具体情况创建。
所以,我们在给类簇类做method swizzle并不能用[self class]获得其真实的类名。
比如,我们可以通过以下代码来得到NSMutableArray的真实类型:

object_getClass([[NSMutableArray alloc] init]);
objc_getClass("__NSArrayM");

上面代码中__NSArrayM是NSMutableArray的真实类名;
对应的一些常见类簇的真实类名:

真实类名
NSArray __NSArrayI
NSMutableArray __NSArrayM
NSDictionary __NSDictionary
NSMutableDictionaary __NSDictionaryM

3.3、为什么在+load()方法中实现swizzling

  • load在源文件被程序装载时自动调用(并不能手动调用,与该类是否被使用无关),在main()之前执行。
  • 当子类重写了load,并且子类的类别重写了loadload的调用顺序:父类、子类、子类类别。
  • 如果有多个子类category都重写了load,每个子类category的load都会被调用一次。
  • 如果子类没有重写load,子类的默认load不会去调用父类的load,这里与正常的继承不一样。
  • 可以根据Compile Sources的装载顺序,简单地判断各个类load的调用顺序,但是有继承关系的类,顺序为父类的方法先调用。
  • 一般来说,除了method swizzle,其他的逻辑尽量不要不要放在load中,该方法中的逻辑尽量简单。
  • 这里补充说一下initialize这个方法:第一次给一个类发送消息,调用initialize(与是否使用相关),只会调用一次,initializeload的顺序没有固定先后;不能显示调用initialize,即使子类没有重写该方法,子类的默认initialize也会调用其父类的initialize;主要用来对一些不方便在编译器初始化的对象赋值,编译时期没有runtime。
  • loadinitialize内部都使用了锁,都是现成安全的。

3.4、为什么swizzling的实现要放在dispatch_once

什么情况下,+load会被调用两次?

3.5、第三方jrswizzle

4、使用消息转发实现(分析Aspect源码)

4.1、Aspect源码的实现思路

对于待 hook 的 selector,将其指向 objc_msgForward / _objc_msgForward_stret;同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hook 住 forwardInvocation 函数,使他指向自己的实现。

当被 hook 的 selector 被执行的时候,首先根据 selector 找到了 objc_msgForward / _objc_msgForward_stret ,而这个会触发消息转发,从而进入 forwardInvocation。同时由于 forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation 函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP。

Aspect源码的关键流程:

4.2、Aspect源码的简单介绍

  • AspectOptions枚举类型,表示block执行的时机。
  • AspectsContainer表示对象或者类的所有的 Aspects 整体情况。
  • AspectIdentifier表示一个 Aspect 的具体内容,包含了单个的 aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等。
  • AspectInfo表示一个 Aspect 执行环境,主要是 NSInvocation 信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容