demo地址如下
https://github.com/HongXiuTanXiang/runtimedemo.git
更多用法
https://juejin.im/entry/584912648e450a006c4be90a
在开始之前,要先了解一些概念
isa指针,这个是一个Class 类型的数据而Class是个结构体指针,所以,isa就是个结构体指针,只不过在结构体的首地址处罢了,根据这个isa的指针偏移,就可以在结构体中取得我们想要的数据
SEL 方法选择器 只是描述了一个方法的格式,猜测是结构体指针类型
IMP 方法实现地址,就是函数指针
typedef id (*IMP)(id, SEL, ...)
SEL相当于在.h头文件中方法的声明,IMP就是.m中对应的方法的实现
- 在runtime.h头文件中有以下一些结构体和结构体指针的定义,从这里我们可以看到oc中的类和对象的数据类型,这有
助于我们理解runtime
struct objc_method {
SEL method_name
char *method_types
IMP method_imp
}
typedef struct objc_method *Method;
struct objc_ivar {
char *ivar_name
char *ivar_type
int ivar_offset
int space
}
typedef struct objc_ivar *Ivar;
struct objc_category {
char *category_name
char *class_name
struct objc_method_list *instance_methods
struct objc_method_list *class_methods
struct objc_protocol_list *protocols
}
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
}
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
struct objc_selector{
void *sel_id;
const char *sel_types;
};//这一点是猜测的在runtime的源码内没有找到具体的objc_selector定义
typedef struct objc_selector *SEL;
oc 中的类其实也是对象,是元类的实例对象,而oc中对类的定义,就算是一个结构体
结构体内部有个 Class isa指针,这个指针指向元类的首地址,元类也是对象,是根元类的
实例对象,oc中对对象的定义也是一个结构体,内部也有个Class isa指针,这个指针指向
他对应的类,而这个类中就存储这个实例对象的对象方法,属性,版本信息,协议等,类方法在
元类中存储,静态变量也在元类中存储,
1 实例对象由类生成,
2 类由元类生成,类本质上也是对象
3 元类由根元类生成
4 根元类的isa指针指向自己
在cocao中,NSobject是一切类的起始点,也就是所有类的元类,但是苹果并没有告诉我们NSobject他的元类是什么
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
NSObject的isa指针NSObjectMetaClass(终极meta class),NSObjectMetaClass isa指针指向自己;
NSObject的super_class指针指向nil,NSObjectMetaClass的super_class指向NSObject;
元类(Meta Class)的解释
meta-class是一个类对象的类。
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么,这个isa指针指向什么呢?
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。
所以,调用类方法的这个类对象的isa指针指向的就是meta-class
当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。
即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下代码
这样就形成了一个内部的闭环
借网图一张,更加清晰

runtime在实际应用中的使用
使用runtime要导入#import <objc/message.h>
并且需要对项目环境做一点配置,如下图

1用于交换方法
/**
方法交换
当我们调用系统某个方法,但是发现不能满足使用要求是,
那么我们可以自定义一个分类,然后再分类中使用方法交换,来实现偷梁换柱
*/
- (void)test0 {
UIImage *mm = [UIImage imageNamed:@"icon104.png"];
NSLog(@"%@",mm);
UIImage *mm1 = [[UIImage alloc]initWithData:[NSData data]];
NSLog(@"%@",mm1);
}
分类中的实现
//.h文件
#import <UIKit/UIKit.h>
@interface UIImage (EXT)
+(instancetype)imageWithName:(NSString *)name;
@end
//-----------------------
//.m文件
#import "UIImage+EXT.h"
#import <objc/message.h>
@implementation UIImage (EXT)
//Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading
//当类被加载入oc runtime时候调用这个方法
+(void)load{
//这种交换是对等的,如果外界调用imageWithName,那实际上是调用imageNamed
//反之,如果调用imageNamed,实际上调用的是imageWithName
//类方法交换
Method m = class_getClassMethod(self, @selector(imageWithName:));
Method mm = class_getClassMethod(self, @selector(imageNamed:));
method_exchangeImplementations(m, mm);
//对象方法交换
Method m1 = class_getInstanceMethod(self, @selector(initWithMyData:));
Method mm1 = class_getInstanceMethod(self, @selector(initWithData:));
method_exchangeImplementations(m1, mm1);
}
-(instancetype)initWithMyData:(NSData*)data{
UIImage *image = [self initWithMyData:data];
if (data == nil) {
NSLog(@"data == nil");
}
if (image == nil) {
NSLog(@"image == nil");
}
return image;
}
+(instancetype)imageWithName:(NSString *)name{
//这里的内存是谁来分配的呢,肯定是编译器底层自己根据判断来分配的
/**
3. 此时调用的方法 'imageWithName:' 相当于调用系统的 'imageNamed:' 方法,原因是在load方法中进行了方法交换.
4. 注意:此处并没有递归操作.
代码调用了[self imageWithName:name],看起来会陷入死循环,但其实imageWithName:的实现已经是imageNamed(IMP),因此该调用相当于调用系统原来的生成图片的实现。
*/
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"image == nil");
}
return image;
}
@end
2动态给某个类添加方法
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
//--------------------
#import "Person.h"
#import <objc/message.h>
@implementation Person
void person_rum(id self,SEL sel){
NSLog(@"动态添加run方法");
}
void person_smile(id self,SEL sel,id argument){
NSLog(@"动态添加smile方法");
}
void person_eat(id self,SEL sel){
NSLog(@"动态添加eat方法");
}
/**
如果类或者对象调用方法的时候,发现没有实现这个方法,会崩溃,但是,我们可以利用运行时动态添加方法
这样,我们就可以现在类里面用c语言格式写好很多待调用的方法,然后,外面传过来字符串,根据字符串来动态
调用方法,这样就实现了动态处理
*/
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == NSSelectorFromString(@"run")) {
class_addMethod(self, sel, (IMP)person_rum, "v@:");
}else if (sel == NSSelectorFromString(@"smile")){
//v == void 返回值,@ == self,: == sel,@ == id
class_addMethod(self, sel,(IMP)person_smile, "v@:@");
}
return [super resolveInstanceMethod:sel];
}
+(BOOL)resolveClassMethod:(SEL)sel{
if (sel == NSSelectorFromString(@"eat")) {
class_addMethod(self, sel, (IMP) person_eat, "v@:");
}
return [super resolveClassMethod:sel];
}
//上面两个方法不能确定方法的实现时候就会来到这里,这个返回值id不能是self
//这个方法主要是把消息转发给一个能够处理这个消息的对象
//hook功能就是用这个入口来实现的
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
//如果上面这个方法返回的是nil那么就会来到这个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"rum"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
//如果实现了上面的方法,最后会来到这里
- (void)forwardInvovation:(NSInvocation*)anInvocation
{
[anInvocation setSelector:@selector(run)];
[anInvocation invokeWithTarget:self];
}
@end
3在分类中添加属性
#import <Foundation/Foundation.h>
@interface NSObject (property)
@property (nonatomic, strong) NSString *name;
@end
------------------------------
#import "NSObject+property.h"
#import <objc/message.h>
static const char * key= "key";
@implementation NSObject (property)
-(void)setName:(NSString *)name{
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
return objc_getAssociatedObject(self, key);
}
@end
使用runtime获取类的成员变量
在开发中经常有些需求,凭借着苹果提供的API不好实现,或者实现起来比较麻烦。此时,
我们就可以运用runtime来获取类的内部成员变量,然后运用KVC进行替换,来达到目的。
下面就贴出运用runtime来获取内部成员变量的方法
对UIGestureRecognizer
var count: UInt32 = 0
let ivars = class_copyIvarList(UIGestureRecognizer.self, &count)
for i in 0 ..< count {
let ivar = ivars![Int(i)]
let cname = ivar_getName(ivar)
let stringname = String(cString: cname!, encoding: .utf8)!
print(stringname)
}
//运行结果
_gestureFlags
_targets
_delayedTouches
_delayedPresses
_view
_lastTouchTimestamp
_state
_allowedTouchTypes
_initialTouchType
_internalActiveTouches
_forceClassifier
_requiredPreviewForceState
_touchForceObservable
_touchForceObservableAndClassifierObservation
_forceTargets
_forcePressCount
_beganObservable
_failureRequirements
_failureDependents
_delegate
_allowedPressTypes
_gestureEnvironment
实现全屏pop
思路
-
1创建一个继承自UINavigationController的导航控制器
-
2然后利用运行时获取到成员属性
-
3然后给控制器的View添加一个手势,实现全屏pop
以下是实现
import UIKit
class YSNAVController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// 1.使用运行时, 打印手势中所有属性
guard let targets = interactivePopGestureRecognizer!.value(forKey: "_targets") as? [NSObject] else { return }
print(targets)
let targetObjc = targets[0]
let target = targetObjc.value(forKey: "target")
let action = Selector(("handleNavigationTransition:"))
let panGes = UIPanGestureRecognizer(target: target, action: action)
self.view.addGestureRecognizer(panGes)
test0()
}
//runtime获取成员属性
func test0() {
var count: UInt32 = 0
let ivars = class_copyIvarList(UIGestureRecognizer.self, &count)
for i in 0 ..< count {
let ivar = ivars![Int(i)]
let cname = ivar_getName(ivar)
let stringname = String(cString: cname!, encoding: .utf8)!
print(stringname)
}
}
}
网友评论