runtime

作者: AntKing | 来源:发表于2017-08-16 15:20 被阅读0次

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类的一个继承体系了,如下代码

这样就形成了一个内部的闭环

借网图一张,更加清晰

5DDB1327-190F-409E-8C6C-CB4CF7006ACE.png

runtime在实际应用中的使用

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

01.png

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)
            
        }
    }


  
    
}


相关文章

网友评论

      本文标题:runtime

      本文链接:https://www.haomeiwen.com/subject/xxmirxtx.html