iOS首页投稿(暂停使用,暂停投稿)iOS 开发

iOS Method Swizzle黑魔法小记

2016-09-30  本文已影响415人  杰米

本文主要内容为用runtime实现Swizzle,即调换两个方法的实现

一点iOS runtime的基本知识

Objective-C是一门动态语言,每个方法在运行时会被动态转为消息发送,方法的实现由运行时决定

[foo message:@"ss"] 向foo对象发送message的消息会被runtime转换成

((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")

当然在代码里上面这两个方法都是等价的,实现效果都一样

objc_msgSends是一个类型为((void*)(id,SEL,id))的函数指针。

下面说说objc_msgSends的机制


看看Objective-C中对象的定义

对象定义
类定义

如上面[foo message:@"ss"] 中的foo对象是objc_object结构类型的,他里面有一个isa指针指向他的类对象Class,所以foo对象的所有信息都可以在Class里面找到,如实例对象,方法列表等

当执行[foo message:@"ss"]的时候

  1. [foo message:@"ss"]转换为
((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")
  1. 以上述[foo message:@"ss"]为例子,runtime会根据foo对象的isa指针找到该对象所属的类,然后以@selector(message)为键在该对象的方法列表(methodLists)寻找方法运行,若最终找不到方法,runtime提供3次机会将方法进行处理,否则最后会抛出unrecognized selector sent to XXX的异常
  2. 三次补救机会看Objective-C Runtime 1小时入门教程
本文的Swizzle黑魔法从上面第二点入手,替换methodLists中的方法

本文例子,替换UIViewcontroller的viewdidLoad方法
新建SwizzleHelper类

屏幕快照 2016-09-30 下午5.46.29.png
  1. 首先用class_getInstanceMethod获取UIViewController的ViewdidLoad方法和SwizzleHelper的
    jm_viewDidLoad方法
    Mehod是一个结构体,如下
Method结构
  1. class_addMethod向UIViewController类中添加jm_viewDidLoad方法
  2. 再获取添加后UIViewController中的jm_viewDidLoad方法
  3. method_exchangeImplementations将UIViewController中的ViewdidLoad方法和jm_viewDidLoad的实现进行替换
屏幕快照 2016-09-30 下午7.35.20.png

运行后打印,证明方法已被替换

屏幕快照 2016-09-30 下午6.07.45.png

再说说下面这个替换的方法


屏幕快照 2016-09-30 下午6.14.04.png

为什么再调用一次呢,因为jm_viewDidLoad方法的实现已被替换,jm_viewDidLoad对应的是原viewdidLoad方法的实现。

替换后调用过程

当UIViewController对象调用viewDidLoad的时候,会转成((void*)(id,SEL,id))(objc_msgSend)((UIViewcontroller对象),@selector(viewDidLoad)),此时runtime会根据UIViewcontroller对象的isa指针找到该对象所属的类,然后以@selector(viewDidLoad)为键在该对象的方法列表 (methodLists)寻找方法运行,此时viewDidLoad方法的实现已经替换为jm_viewDidLoad的实现方法,从而实现了方法替换.

思考1

屏幕快照 2016-09-30 下午7.16.44.png

尝试不将jm_viewDidLoad方法加入UIViewcontroller类里面,然后直接替换jm_viewDidLoad和viewdidLoad方法的实现,却出现下面异常。

屏幕快照 2016-09-30 下午7.21.32.png

为什么呢?

因为虽然实现替换成功了,但由于jm_viewDidLoad里面又调用了一次[self jm_viewDidLoad],又由于UIViewcontroller中没有添加jm_viewDidLoad方法,所以调用[self jm_viewDidLoad]时会找不到方法从而抛出异常。

思考2

为什么在+(void)load方法中进行替换呢?

+(void)load方法在main()前类载入内存时会调用一次。详细看
NSObject的load和initialize方法
你真的了解 Objective-C 中的load 方法么?

Demo下载

Demo下载地址

参考

iOS class_addMethod使用
说说objcRuntime的一些妙用(class_addMethod,class_replaceMethod)
iOS开发:Runtime黑魔法,替换系统类方法及属性
Objective-C Runtime 1小时入门教程

上一篇下一篇

猜你喜欢

热点阅读