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()方法的替代