Runtime总结


title: Runtime总结
date: 2019-07-25 10:26:17
tags:

一、简介

OC中的runtime是用c和汇编写的运行时的库,将数据类型的确定由编译时推迟到了运行时,oc代码最终都会转换成runtime的c语言代码,oc需要runtim来创建类和对象,进行消息发送和转发。

对于我们熟悉的C语言,函数的调用在编译的时候会决定调用哪个函数。但对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用

二、主要api

class_copyPropertyList 获取类的属性列表

class_copyMethodList 获取类的方法列表

class_copyProtocolList获取类的协议列表

objc_getAssociatedObject获取关联对象

objc_setAssociatedObject绑定关联对象

Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);获取实例方法

method_exchangeImplementations交换方法实现

class_replaceMethod替换原方法实现

class_getClassMethod获取类方法

三、oc使用方法

1.动态获取类的属性

*NSObject分类中的实现

#import "NSObject+runtime.h"
#import <objc/runtime.h>
@implementation NSObject (runtime)
// 获取属性名称列表*
+ (NSArray *) getPropertyArray{
	 //当Person对象属性已经获取的时候,就直接返回,防止多次调用运行时方法提高效率
    const char * kPropertiesListKey = "CZPropertiesListKey";
    NSArray * ptyList = objc_getAssociatedObject(self, kPropertiesListKey);
    if (ptyList != nil) {
        return  ptyList;
    }
   unsigned int count = 0;
   //objc_property_t是c语言的结构体指针*
   objc_property_t * list = class_copyPropertyList([self class], &count);
   NSLog(@"属性的数量:%d",count);
   NSMutableArray * arr = [[NSMutableArray alloc]init];
   for (int i = 0; i < count; i ++) {
     objc_property_t pro = list[i];
     const char * cName = property_getName(pro);
     NSString * name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
     NSLog(@"%@", name);
     [arr addObject:name];
   }
   free(list);
    //绑定关联对象
    objc_setAssociatedObject(self, kPropertiesListKey, [arr copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return  [arr copy];
}
@end

*可以使用上述获取类属性的方法来实现字典转模型

+ (instancetype)objWithDict:(NSDictionary *)dict {
   id object = [[self alloc] init];
   NSArray * proList = [self getPropertyArray];
   [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
     if ([proList containsObject:key]) {
        [object setValue:obj forKey:key];
     }
   }];
   return  object;
}

2.拦截系统自带的方法调用(交换类的方法 )

*UIViewController分类中的实现,可用于实现AOP数据埋点

# import "UIViewController+Analysis.h"
#import <objc/runtime.h>
@implementation UIViewController (Analysis)

// 在类被加载到运行时的时候,就会执行
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       SEL originSelector = @selector(viewDidLoad);
        SEL swizzleSelector = @selector(userViewDidLoad);
        Method originalMethod = class_getInstanceMethod([self class], originSelector);
        Method swizzleMethod = class_getInstanceMethod([self class], swizzleSelector);
        
        BOOL didAddMethod = class_addMethod([self class], originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAddMethod) {
            class_replaceMethod([self class], swizzleSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzleMethod);
        }
    });
}
- (void)userViewDidLoad {
    [self userViewDidLoad]; 
    NSString *identifier = [NSString stringWithFormat:@"%@",[self class]];
    NSLog(@"identifier:%@",identifier);
}
@end

3.给分类添加属性

@interface Person (CategoryName)
@property (nonatomic,copy) NSString * str;
@end

\#import "Person+CategoryName.h"
\#import <objc/runtime.h>
//给分类添加属性
const void *PersonStrKey = @"PersonStrKey";
@implementation Person (CategoryName)

\- (void *)setStr:(NSString *)str {
    objc_setAssociatedObject(**self**, &PersonStrKey, str, OBJC_ASSOCIATION_COPY);
}

-(NSString *)str {
    return objc_getAssociatedObject(self, &PersonStrKey);
}
@end

四、swift中的使用

*纯Swift类的函数的调用已经不是OC的运行时发送消息,和C类似,在编译阶段就确定了调用哪一个函数,所以纯Swift的类我们是没办法通过运行时去获取到它的属性和方法的。

var count: UInt32 = 0
let swiftClass = TestSwiftRunTime()
let proList = class_copyPropertyList(object_getClass(swiftClass), &count)
for i in 0..<count {
     let property = property_getName(proList![Int(i)])
     print("属性:\(String(cString: property))")
}

上面TestSwiftRunTime类是swift类,所以count = 0

*Swift 对于继承自OC的类,为了兼容OC,凡是继承与OC的都是保留了它的特性的,所以可以使用Runtime获取到它的属性和方法等等其他我们在OC中获得的东西

所以现在将TestSwiftRunTime继承自NSObject,并且注意swift4.0之后想要使用class_copyPropertyList获取属性列表的话我们就需要在class前面加上@objcMembers 这么一个关键字就可以了

@objcMembers class TestSwiftRunTime: NSObject {
    var aBool: Bool = true
    var aInt: Int = 0
    func testReturnVoidWidthaId(aId: UIView) {
        print("TestSwiftRunTime.testReturnVoidWidthaId")
    }
}

得到属性数量是2

2.拦截系统自带的方法调用(交换类的方法 )

import Foundation
import UIKit
extension UIViewController {
class func initialMethod() {
        let originSelector = #selector(viewDidLoad)
        let swizzleSelector = #selector(userViewDidload)
        guard let originalMethod = class_getInstanceMethod(self, originSelector) else { return  }
        guard let swizzledMethod = class_getInstanceMethod(self, swizzleSelector) else { return  }
        let didAddMethod = class_addMethod(self, originSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if didAddMethod {
            class_replaceMethod(self, swizzleSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
   }

@objc func userViewDidload() {
        self.userViewDidload()
       print("类名\(String(describing: object_getClass(**self**)))")
  }
}

上面oc实现中的load方法在swift中已经没有了,所以要在appdelegate里面调一下这个方法

UIViewController.initialMethod()

3.给扩展添加属性

var dogKey = "dogKey"
extension Dog {
    var str: String? {
        set {
            objc_setAssociatedObject(self, dogKey, newValue, .OBJC_ASSOCIATION_COPY)
        }     
        get {
            return objc_getAssociatedObject(self, dogKey) as? String
        }
    }
}

五、使用场景

只贴swift的代码,记住swift没有load方法,要在appdelegate里调用一次

1.防止button暴力点击

import Foundation
import UIKit
struct RuntimeButtonKey {
    static let timeInterval = UnsafeRawPointer(bitPattern: "timeInterval".hashValue)
}
extension UIButton {
    var time: CGFloat? {
        set {
            objc_setAssociatedObject(**self**, RuntimeButtonKey.timeInterval!, newValue, .OBJC_ASSOCIATION_COPY)
        }
        get {
            return objc_getAssociatedObject(self, RuntimeButtonKey.timeInterval!) as? CGFloat
        }
    }    
    class func initialMethod() {
        let originSelector = #selector(sendAction(_:to:for:))
        let customSelector = #selector(userSendAction(_:to:for:))
        guard let systemMethod = class_getInstanceMethod(self, #selector(sendAction(_:to:for:))) else { return  }
        guard let customMethod = class_getInstanceMethod(self, #selector(userSendAction(_:to:for:))) else { return  }
        
        let didAddMethod = class_addMethod(UIControl.self, originSelector, method_getImplementation(customMethod), method_getTypeEncoding(customMethod))
        if didAddMethod {
            class_replaceMethod(UIControl.self, customSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod))
        } else {
            method_exchangeImplementations(systemMethod, customMethod)
        }
    }    
    @objc private func  userSendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
        self.userSendAction(action, to: target, for: event)
        self.isUserInteractionEnabled = false       
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
            self.isUserInteractionEnabled = true
        }
    }
}

2.防止UITapGestureRecognizer的暴力点击

import Foundation
import UIKit

extension UITapGestureRecognizer {
    class func initialMethod() {
    ///替换设置delegate的方法
        let originSelector = #selector(setter: self.delegate)
        let swizzleSelector = #selector(self.nsh_setDelegate(delegate:))
        guard let originMethod = class_getInstanceMethod(UITapGestureRecognizer.self, originSelector) else { return  }
        guard let swizzleMethod = class_getInstanceMethod(UITapGestureRecognizer.self, swizzleSelector) else { return  }

        let didAddMethod = class_addMethod(UITapGestureRecognizer.self, originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
        if didAddMethod {
            class_replaceMethod(UITapGestureRecognizer.self, swizzleSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod))
        } else {
            method_exchangeImplementations(originMethod, swizzleMethod)
        }
     }

    @objc func nsh_setDelegate(delegate: UIGestureRecognizerDelegate?) {
        self.nsh_setDelegate(delegate: delegate)

        guard let nDelegate = delegate else {
            return
        }
				/// 替换delegate中手势触摸开始的方法
        let originSelector = #selector(nDelegate.gestureRecognizerShouldBegin(_:))
        let swizzleSelector = #selector(UITapGestureRecognizer.nsh_gestureRecognizerShouldBegin(_:))
        guard let originMethod = class_getInstanceMethod(type(of: nDelegate), originSelector) else { return  }
        guard let swizzleMethod = class_getInstanceMethod(UITapGestureRecognizer.self, swizzleSelector) else { return  }

        let didAddMethod = class_addMethod(type(of: nDelegate), swizzleSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
        if didAddMethod {
            guard let didoriginMethod = class_getInstanceMethod(type(of: nDelegate), originSelector) else { return  }
            guard let didswizzleMethod = class_getInstanceMethod(type(of: nDelegate), NSSelectorFromString("nsh_gestureRecognizerShouldBegin:")) else { return  }
            method_exchangeImplementations(didoriginMethod, didswizzleMethod)
        }
    }

    @objc func nsh_gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        self.isEnabled = false
        /// 0.5秒后才恢复触摸
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
            self.isEnabled = true
        }
        
        return self.nsh_gestureRecognizerShouldBegin(gestureRecognizer)
    }
}

3.无侵入埋点

(1)UIControl的点击事件

import Foundation
import UIKit

extension UIControl {
    
    class func initialMethod() {
        let originSelector = #selector(sendAction(_:to:for:))
        let customSelector = #selector(userSendAction(_:to:for:))
        guard let systemMethod = class_getInstanceMethod(self, #selector(sendAction(_:to:for:))) else { return  }
        guard let customMethod = class_getInstanceMethod(self, #selector(userSendAction(_:to:for:))) else { return  }
        
        let didAddMethod = class_addMethod(UIControl.self, originSelector, method_getImplementation(customMethod), method_getTypeEncoding(customMethod))
        if didAddMethod {
            class_replaceMethod(UIControl.self, customSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod))
        } else {
            method_exchangeImplementations(systemMethod, customMethod)
        }
    }
    
    @objc private func  userSendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
        self.userSendAction(action, to: target, for: event)
        
        print("\(action)\(NSStringFromClass(object_getClass(target)!))")
    }
}

*打印出target和action就可以作为事件的唯一标志符

(2)tableView的点击cell事件

import Foundation
import UIKit

extension UITableView {
    交换设置delegate的方法
    class func initialMethod() {
        let originSelector = #selector(setter: self.delegate)
        let swizzleSelector = #selector(self.nsh_setDelegate(delegate:))
        guard let originMethod = class_getInstanceMethod(UITableView.self, originSelector) else { return  }
        guard let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleSelector) else { return  }
        let didAddMethod = class_addMethod(UITableView.self, originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
        if didAddMethod {
            class_replaceMethod(UITableView.self, swizzleSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod))
        } else {
            method_exchangeImplementations(originMethod, swizzleMethod)
        }
    }
    
    @objc func nsh_setDelegate(delegate: UITableViewDelegate?) {
        self.nsh_setDelegate(delegate: delegate)
        
        guard let nDelegate = delegate else {
            return
        }
        
         交换delegate中点击cell的方法
        let originSelector = #selector(nDelegate.tableView(_:didSelectRowAt:))
        let swizzleSelector = #selector(UITableView.nsh_tableView(_:didSelectRowAt:))
        guard let originMethod = class_getInstanceMethod(type(of: nDelegate), originSelector) else { return  }
        guard let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleSelector) else { return  }
        let didAddMethod = class_addMethod(type(of: nDelegate), swizzleSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
        if didAddMethod {
            guard let didoriginMethod = class_getInstanceMethod(type(of: nDelegate), originSelector) else { return  }
            guard let didswizzleMethod = class_getInstanceMethod(type(of: nDelegate), NSSelectorFromString("nsh_tableView:didSelectRowAt:")) else { return  }
            method_exchangeImplementations(didoriginMethod, didswizzleMethod)
        }
    }
    
    @objc func nsh_tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)  {
        self.nsh_tableView(tableView, didSelectRowAt: indexPath)
    		///打印出tableview的delegate对象类名,也就是viewcontroller
        print("\(NSStringFromClass(object_getClass(tableView.delegate)!))")
        let cell = tableView.cellForRow(at: indexPath)
        ///打印出cell 的类名
        print("\(NSStringFromClass(object_getClass(cell)!))")
        
        ///viewcontroller加上cell就可以作为区分的标志符
        
    }
}

(3)手势的点击事件

OC代码

#import "UIGestureRecognizer+logger.h"
#import <objc/runtime.h>

@interface UIGestureRecognizer()

@property (nonatomic, copy) NSString *clazzName;
@property (nonatomic, copy) NSString *actionName;

@end

@implementation UIGestureRecognizer (logger)

- (NSString *)clazzName {
    return objc_getAssociatedObject(self, @selector(clazzName));
}

- (void)setClazzName:(NSString *)className {
    objc_setAssociatedObject(self, @selector(clazzName), className, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)actionName {
    return objc_getAssociatedObject(self, @selector(actionName));
}

- (void)setActionName:(NSString *)actionName {
    objc_setAssociatedObject(self, @selector(actionName), actionName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    		//*******交换init方法,swift交换不了init方法,所以实现不了*********
    
        SEL fromSelector = @selector(initWithTarget:action:);
        SEL toSelector = @selector(yy_initWithTarget:action:);
        Method fromMethod = class_getInstanceMethod(self, fromSelector);
        Method toMethod = class_getInstanceMethod(self, toSelector);
        
        if (class_addMethod(self, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
            class_replaceMethod(self, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
        } else {
            method_exchangeImplementations(fromMethod, toMethod);
        }
    });
}

- (instancetype)yy_initWithTarget:(id)target action:(SEL)action {
    SEL fromSelector = action;
    SEL toSelector = @selector(yy_action:);
    UIGestureRecognizer *originGesture = [self yy_initWithTarget:target action:action];
 
    // 1 过滤 target 和 action 为null的情况
    if (!target || !action) {
        return originGesture;
    }
    
    // 2 过滤 系统类 调用的 initWithTarget:action: 方法
    NSBundle *mainB = [NSBundle bundleForClass:[target class]];
    if (mainB != [NSBundle mainBundle]) {
        return originGesture;
    }
    
    // 3
    Method method = class_getInstanceMethod([self class], toSelector);
    if (class_addMethod([target class], toSelector, method_getImplementation(method), method_getTypeEncoding(method))) {
        Method fromMethod = class_getInstanceMethod([target class], fromSelector);
        Method toMethod = class_getInstanceMethod([target class], toSelector);
        
        if (class_addMethod([target class], fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
            class_replaceMethod([target class], toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
        } else {
            method_exchangeImplementations(fromMethod, toMethod);
        }
    }
    NSLog(@"---->>>target: %@", [target class]);
    NSLog(@"----<<<action: %@", NSStringFromSelector(action));
    
    
    ///把target和action存起来,点击的时候作为唯一标志符
    
    self.clazzName = NSStringFromClass([target class]);
    self.actionName = NSStringFromSelector(action);
    return originGesture;
}

- (void)yy_action:(UIGestureRecognizer *)gesture {
    [self yy_action:gesture];
    NSLog(@"点击了%@方法,位于:%@", gesture.actionName, gesture.clazzName);
}

@end

**交换init方法,init初始化的时候把target和action存起来,点击的时候作为唯一标志符

4.扩大按钮的点击范围

struct ButtonKey {
    static let ButtonTopEdgeKey = UnsafeRawPointer(bitPattern: "ButtonTopEdgeKey".hashValue)
    static let  ButtonBottomEdgeKey = UnsafeRawPointer(bitPattern: "ButtonTopEdgeKey".hashValue)
    static let  ButtonLeftEdgeKey = UnsafeRawPointer(bitPattern: "ButtonTopEdgeKey".hashValue)
    static let  ButtonRightEdgeKey = UnsafeRawPointer(bitPattern: "ButtonTopEdgeKey".hashValue)
}

import Foundation
import UIKit
//扩大按钮的点击范围
extension UIButton {
    var topEdge: CGFloat? {
        set {
            objc_setAssociatedObject(self, ButtonKey.ButtonTopEdgeKey!, newValue, .OBJC_ASSOCIATION_COPY)
        }
        
        get {
            return objc_getAssociatedObject(self, ButtonKey.ButtonTopEdgeKey!) as? CGFloat
        }
    }
    
    var bottomEdge: CGFloat? {
        set {
            objc_setAssociatedObject(self, ButtonKey.ButtonBottomEdgeKey!, newValue, .OBJC_ASSOCIATION_COPY)
        }
        
        get {
            return objc_getAssociatedObject(self, ButtonKey.ButtonBottomEdgeKey!) as? CGFloat
        }
    }
    
    var leftEdge: CGFloat? {
        set {
            objc_setAssociatedObject(self, ButtonKey.ButtonLeftEdgeKey!, newValue, .OBJC_ASSOCIATION_COPY)
        }
        
        get {
            return objc_getAssociatedObject(self, ButtonKey.ButtonLeftEdgeKey!) as? CGFloat
        }
    }
    
    var rightEdge: CGFloat? {
        set {
            objc_setAssociatedObject(self, ButtonKey.ButtonRightEdgeKey!, newValue, .OBJC_ASSOCIATION_COPY)
        }
        
        get {
            return objc_getAssociatedObject(self, ButtonKey.ButtonRightEdgeKey!) as? CGFloat
        }
    }
    
    func setEnlargeEdge(top: CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat) {
        self.topEdge = top
        self.bottomEdge = bottom
        self.leftEdge = left
        self.rightEdge = right
    }
    
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let top = self.topEdge ?? 0
        let bottom = self.bottomEdge ?? 0
        let right = self.rightEdge ?? 0
        let left = self.leftEdge ?? 0
        
        let rect = CGRect(x: self.bounds.origin.x - left, y: self.bounds.origin.y - top, width: self.bounds.size.width + left + right, height: self.bounds.size.height + top + bottom)
        if rect.contains(point) {
            return self
        } else {
            return nil
        }
    }
}

六、参考链接

http://www.cocoachina.com/articles/23476 runtime使用案例

https://www.jianshu.com/p/41714aba7ec1 iOS Runtime基础学习

https://www.cnblogs.com/taoxu/p/7975984.html swift runtime

https://blog.youkuaiyun.com/chenqiangblock/article/details/80180566 Swift4.0中Runtime method_exchangeImplementations的使用和initialize()方法的替代

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值