网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 后端开发 > Swift >

OC方法交换swizzle详细介绍——不再有盲点

时间:2019-06-29 01:16

人气:

作者:admin

标签:

导读:原文链接:https://www.cnblogs.com/mddblog/p/11105450.html 如果对方法交换已经比较熟悉,可以跳过整体介绍,直接看常见问题部分 整体介绍 方法交换是runtime的重要体现,也是quot;消息语言quot;的...

原文链接:https://www.cnblogs.com/mddblog/p/11105450.html

如果对方法交换已经比较熟悉,可以跳过整体介绍,直接看常见问题部分

整体介绍

方法交换是runtime的重要体现,也是"消息语言"的核心。OC给开发者开放了很多接口,让开发者也能全程参与这一过程。

原理

oc的方法调用,比如[self test]会转换为objc_msgSend(self,@selfector(test))。objc_msgsend会以@selector(test)作为标识,在方法接收者(self)所属类(以及所属类继承层次)方法列表找到Method,然后拿到imp函数入口地址,完成方法调用。

typedef struct objc_method *Method;

// oc2.0已废弃,可以作为参考
struct objc_method {
    SEL _Nonnull method_name;
    char * _Nullable method_types;
    IMP _Nonnull method_imp;
}

基于以上铺垫,那么有两种办法可以完成交换:

  • 一种是改变@selfector(test),不太现实,因为我们一般都是hook系统方法,我们拿不到系统源码,不能修改。即便是我们自己代码拿到源码修改那也是编译期的事情,并非运行时(跑题了。。。)
  • 所以我们一般修改imp函数指针。改变sel与imp的映射关系;
系统为我们提供的接口

typedef struct objc_method *Method;Method是一个不透明指针,我们不能够通过结构体指针的方式来访问它的成员,只能通过暴露的接口来操作。

接口如下,很简单,一目了然:

#import <objc/runtime.h>

/// 根据cls和sel获取实例Method
Method _Nonnull * _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);

/// 给cls新增方法,需要提供结构体的三个成员,如果已经存在则返回NO,不存在则新增并返回成功
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)

/// method->imp
IMP _Nonnull method_getImplementation(Method _Nonnull m);

/// 替换
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types)

/// 跟定两个method,交换它们的imp:这个好像就是我们想要的
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
简单使用

假设交换UIViewController的viewDidLoad方法

/// UIViewController 某个分类

+ (void)swizzleInstanceMethod:(Class)target original:(SEL)originalSelector swizzled:(SEL)swizzledSelector {
    Method originMethod = class_getInstanceMethod(target, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(target, swizzledSelector);
    method_exchangeImplementations(originMethod, swizzledMethod);
}

+ (void)load {
    [self swizzleInstanceMethod:[UIViewController class] original:@selector(viewDidLoad) swizzled:@selector(swizzle_viewDidLoad)];
}
/// hook
- (void)swizzle_viewDidLoad {
    [self swizzle_viewDidLoad];
}

交换本身简单:原理简单,接口方法也少而且好理解,因为结构体定义也就三个成员变量,也难不到哪里去!

但是,具体到使用场景,叠加上其它外部的不稳定因素,想要稳定的写出通用或者半通用交换方法,上面的"简单使用"远远不够的。

下面就详细介绍下几种常见坑,也是为啥网上已有很多文章介绍方法交换,为什么还要再写一篇的原因:不再有盲点

常见问题一、被多次调用(多次交换)

"简单使用"中的代码用于hook viewDidload一般是没问题的,+load 方法一般也执行一次。但是如果一些程序员写法不规范时,会造成多次调用。

比如写了UIViewController的子类,在子类里面实现+load方法,又习惯性的调用了super方法

+ (void)load {
    // 这里会引起UIViewController父类load方法多次调用
    [super load];
}

又或者更不规范的调用,直接调用load,类似[UIViewController load]

为了没盲点,我们扩展下load的调用:
  • load方法的调用时机在dyld映射image时期,这也符合逻辑,加载完调用load。
  • 类与类之间的调用顺序与编译顺序有关,先编译的优先调用,继承层次上的调用顺序则是先父类再子类;
  • 类与分类的调用顺序是,优先调用类,然后是分类;
  • 分类之间的顺序,与编译顺序有关,优先编译的先调用;
  • 系统的调用是直接拿到imp调用,没有走消息机制;

手动的[super load]或者[UIViewController load]则走的是消息机制,分类的会优先调用,如果你运气好,另外一个程序员也实现了UIViewController的分类,且实现+load方法,还后编译,则你的load方法也只执行一次;(分类同名方法后编译的会“覆盖”之前的)

为了保险起见,还是:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceMethod:[UIViewController class] original:@selector(viewDidLoad) swizzled:@selector(swizzle_viewDidLoad)];
    });
}

继续扩展:多次调用的副作用是什么呢?
  • 根据原理,如果是偶数次

结果就是方法交换不生效,但是有遗留问题,这时手动调用

- (void)swizzle_viewDidLoad {
    [self swizzle_viewDidLoad];
}

会引起死循环。

其实,方法交换后,任何时候都不要尝试手动调用,特别是交换的系统方法。实际开发中,也没人会手动调用,这里我们只讨论这种场景的技术及后果,帮助理解

  • 奇数次调用

奇数次之后一切正常。但是,奇数次之前,它会先经历偶数次。

比如,第一次交换,正常,第二次交换,那么相当于没有交换,如果你手动调用了swizzle_viewDidLoad,很明显死循环了,然后你又在其它线程进行第三次交换,又不死循环了。哈哈,好玩,但你要保重,别玩失火了玩到线上了!!!

这种情况还是有可能发生的,比如交换没有放在load方法,又没有dispatch_once,而是自己写了个类似start的开始方法,被自己或者他人误调用。

最后:为了防止多次交换始终加上dispatch_once,除非你清楚你自己在干啥。

再次扩展:常见的多次交换

这里说的多次交换,和上面说的不一样,交换方法不一样,比如我们开发中经常遇到的。

我们自己交换了viewDidLoad,然后第三方库也交换了viewDidLoad,那么交换前(箭头代表映射关系):

sysSel -> sysImp
ourSel -> ourImp
thirdSel -> thirdImp

第一步,我们与系统交换:

sysSel -> ourImp
ourSel -> sysImp
thirdSel -> thirdImp

第二步,第三方与系统交换:

sysSel -> thirdImp
ourSel -> sysImp
thirdSel -> ourImp

假设,push了一个VC,首先是系统的sysSel,那么调用顺序:

thirdImp、ourImp、sysImp

没毛病!

多次交换这种场景是真实存在的,比如我们监控viewDidload/viewWillappear,在程序退到后台时,想停止监控,则再进行一次(偶数)交换也是一种取消监控的方式。当再次进入前台时,则再次(奇数)交换,实现监控。(通过标志位实现用的更多,更简单)

问题二、被交换的类没有实现该方法

我们还是在分类里面添加方法来交换

情况一:父类实现了被交换方法

我们本意交换的是子类方法,但是子类没有实现,父类实现了class_getInstanceMethod(target, swizzledSelector);执行的结果返回父类的Method,那么后续交换就相当于和父类的方法实现了交换。

一般情况下也不会出问题,可是埋下了一系列隐患。如果其它程序员也继承了这个父类。举例代码如下

/// 父类
@interface SuperClassTest : NSObject
- (void)printObj;
@end
@implementation SuperClassTest
- (void)printObj {
    NSLog(@"SuperClassTest");
}
@end

/// 子类1
@interface SubclassTest1 : SuperClassTest
@end
@implementation SubclassTest1
- (void)swiprintObj {
    NSLog(@"printObj");
}
@end

/// 子类1 分类实现交换
@interface SubclassTest1(swiTest)
@end
@implementation SubclassTest1(swiTest)
+ (void)swizzleInstanceMethod:(Class)target original:(SEL)originalSelector swizzled:(SEL)swizzledSelector {
    Method originMethod = class_getInstanceMethod(target, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(target, swizzledSelector);
    method_exchangeImplementations(originMethod, swizzledMethod);
}
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceMethod:[SubclassTest1 class] original:@selector(printObj) swizzled:@selector(swiprintObj)];
    });
}

- (void)swiprintObj {
    NSLog(@"swi1:%@",self);
    [self swiprintObj];
}
@end

/// 子类2
@interface SubclassTest2 : SuperClassTest
@end
@implementation SubclassTest2
/// 有没有重写此方法,会呈现不同的结果
//- (void)printObj {
//    // 有没有调用super  也是不同的结果
//    [super printObj];
//    NSLog(@"printObj");
/ 
温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
  • iOS 17新特性以及适配细节汇总

    iOS 17新特性以及适配细节汇总

    1、UIScrollView增加了属性allowsKeyboardScrolling表示是否根据连接的物理键盘的方向键...
  • 最近几天

    最近几天

    8.03周四 一大早电话吵醒,着急给我妈送卡,早上坐车去延安,顺便下来玩玩,...
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信