runtime用途

什么是Runtime

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行,OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多,利用关联对象给分类添加属性,遍历类的所有成员变量(修改系统类的文字颜色,字典转模型等),交互方法实现,利用消息转发机制降低崩溃率,weak 实现原理,weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

NSMutableArray *array = @[@1, @2];
[array addObject:@"234"];

// 降低unrecongnized selector崩溃率
实现方法签名
给NSObject写一个分类

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    // 本来能调用的方法
    if ([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    }
    //找不到方法
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));// 收集信息传到公司的服务器,看哪个方法找不到
}
拦截所有按钮的点击事件
#import "UIControl+Extension.h"
#import <objc/runtime.h>


@implementation UIControl (Extension)

+ (void)load {
	// 这里注意用的时候用dispatch_once,防止其他地方故意调用Load类方法
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(mhf_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}

- (void)mhf_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    NSLog(@"----self:%@---target:%@----action:%@---",self, target,NSStringFromSelector(action));
    
//    [target performSelector:action];// 调用之前绑定的方法实现
//    [self sendAction:action to:target forEvent:event]; // 这样调用的永远是当前方法,循环调用,死循环 两个方法实现已经交互了
    [self mhf_sendAction:action to:target forEvent:event];
   
    // 拦截了所有按钮的点击事件 [self isKindOfClass:[UIButton class]]
    
}
@end
数组中放入空对象
 NSMutableArray *array = [NSMutableArray array];
    [array addObject:@"abc"];
    [array insertObject:nil atIndex:0];
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)
+ (void)load {
    //类簇   NSString,NSArray,NSDictionary
    Class cls = NSClassFromString(@"__NSArrayM");          //NSMutableArray真正的类型是__NSArrayM
    Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
    Method method2 = class_getInstanceMethod(cls, @selector(mhf_insertObject:atIndex:));
    method_exchangeImplementations(method1, method2);
}

- (void)mhf_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) {
        return;
    }
    [self mhf_insertObject:anObject atIndex:index];
}
@end

Method Swizzling 为什么要先调用 class_addMethod?

//BaseModel.h
@interface BaseModel : NSObject
- (void)baseTest;
- (void)swizzledTest;
@end

//BaseModel.m
#import "BaseModel.h"
@implementation BaseModel
- (void)baseTest {
    NSLog(@"baseTest  BaseTest");
}
@end
// SubModel.h
#import "BaseModel.h"
@interface SubModel : BaseModel
@end

// SubModel.m
#import "SubModel.h"
#import <objc/runtime.h>
@implementation SubModel
+ (void)load {
    Method method1 = class_getInstanceMethod(self, @selector(swizzledTest));
    Method method2 = class_getInstanceMethod(self, @selector(baseTest));
    method_exchangeImplementations(method1, method2);
}
- (void)swizzledTest {
    NSLog(@"swizzledTests---------swizzledTest");
}
//VC.m中调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
  SubModel * m = [[SubModel alloc]init];
    [m swizzledTest];
    
    BaseModel * m1 = [[BaseModel alloc]init];
    [m1 swizzledTest];
    }
  /*打印结果
2020-05-16 21:06:41.505419+0800 OCEssence[11319:375284] baseTest  BaseTest
2020-05-16 21:06:45.091973+0800 OCEssence[11319:375284] -[BaseModel swizzledTest]: unrecognized selector sent to instance 0x6000013045d0
2020-05-16 21:06:45.106981+0800 OCEssence[11319:375284] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseModel swizzledTest]: unrecognized selector sent to instance 0x6000013045d0'
*/

父类有个方法baseTest,子类未重写baseTest方法,子类的中想要拿来替换的方法为swizzledTest
在子类的实例中调用swizzledTest方法时,确实按预期正常运行的
在父类的实例中调用swizzledTest方法时,就开始崩溃了。因为方法交换后,swizzledTest方法的IMP其实和子类baseTest的IMP进行了交换,此时等同于父类调用子类方法,当然会崩溃。

在举栗子前引用一段对 Selectors、Methods 和 Implementations 理解:

理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。

假设父类有个方法method,子类未重写method方法,子类的中想要拿来替换的方法为swizzledMethod。

extension NSObject {
    static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
        guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),
              let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {
            return
        }

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

如果按上述方法
在子类的实例中调用method方法时,确实按预期正常运行的
在父类的实例中调用method方法时,就开始崩溃了。因为方法交换后,method方法的IMP其实和子类swizzledMethod的IMP进行了交换,此时等同于父类调用子类方法,当然会崩溃。

所以应该是

extension NSObject {
    static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
        guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),
              let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {
            return
        }
        
        let isAddSuccess = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if isAddSuccess {
            class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

class_addMethod先判断了子类中是否有method方法

如果有,则添加失败,直接进行交换
如果没有,则添加成功,将swizzledMethod的IMP赋值给method这个Selector,然后在将method的IMP(其实是父类中的实现)赋值给swizzledMethod这个Selector

Swift中的实现要注意

其实 Swift 中实现原理和 OC 基本一致,只是苹果爸爸不再允许在 Swift 中使用+load()和+initialize()方法

可以看到核心实现和 OC 是完全一致的,那么剩下的就是模拟 OC 版本实现中的+load()和dispatch_once。

dispatch_once

我们用viewDidLoad来做个dispatch_once的示范:

extension UIViewController {
    static func swizzleViewDidLoad() {
        _ = self.swizzleMethod
    }
    
    @objc func swizzled_viewDidLoad() {
        swizzled_viewDidLoad()
        print("嘻嘻")
    }
    
    private static let swizzleMethod: Void = {
        let originalSelector = #selector(viewDidLoad)
        let swizzledSelector = #selector(swizzled_viewDidLoad)
        swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
    }()
}

在 Swift 中static let这样声明的变量其实已经用到dispatch_once,而且static自带lazy属性,要在封装函数swizzleViewDidLoad被调用时候才调用。

+load()

OC 中+load()方法会在类被装载时调用,确保需要用到的方法都是被 Swizzling 过的。Swift 中可以在AppDelegate 的init方法中手动调用 swizzle 方法模拟+load()实现。

class AppDelegate: UIResponder, UIApplicationDelegate {
    override init() {
        super.init()
        UIViewController.swizzleViewDidLoad()
    }
}

Method Swizzling 为什么要先调用 class_addMethod?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值