RunTime笔记

#import <objc/runtime.h>


一、基本定义


消息传递的方法:objc_msgsend(id, SEL,...);

Runtime数据结构

SEL

typedef struct objc_selector *SEL;    (其实就是C语言中的char *) 

生成SEL的方法:@selector()、sel_registerName(<#const char * _Nonnull str#>)

SEL转字符串:NSString* NSStringFromSelector(SEL aSelector)

id

typedef struct objc_object *id;

Class

typedef struct objc_class *Class;

Class isa指向Class

常用方法:

NSStringFromClass(Class);

Method 

其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针

typedef struct objc_method *Method;

Ivar 

表示类中的实例变量

typedef struct objc_ivar *Ivar;

常用方法:

  //获取Ivar的名称
  const char *ivar_getName(Ivar v);
  //获取Ivar的类型编码,
  const char *ivar_getTypeEncoding(Ivar v)
  //通过变量名称获取类中的实例成员变量
  Ivar class_getInstanceVariable(Class cls, const char *name)
  //通过变量名称获取类中的类成员变量
  Ivar class_getClassVariable(Class cls, const char *name)
  //获取指定类的Ivar列表及Ivar个数
  Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
  //获取实例对象中Ivar的值
  id object_getIvar(id obj, Ivar ivar) 
  //设置实例对象中Ivar的值
  void object_setIvar(id obj, Ivar ivar, id value)

IMP

IMP本质上就是一个函数指针,指向方法的实现,当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。),创建获取方式:

OBJC_EXPORT IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) ;

OBJC_EXPORT IMP _Nullable
class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name);

Cache

Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。

消息发送

objc_msgSend的具体流程如下:

通过isa指针找到所属类查找类的cache列表, 如果没有则下一步查找类的”方法列表”如果能找到与选择子名称相符的方法, 就跳至其实现代码找不到, 就沿着继承体系继续向上查找如果能找到与选择子名称相符的方法, 就跳至其实现代码找不到, 执行”消息转发”.

objc_msgSend和objc_msgSendSuper(一个用于当前类中、一个用于父类中)

objc_msgSend发送消息逻辑:

  1. 首先根据receiver对象的isa指针获取它对应的class;
  2. 优先在class的cache查找message方法,如果找不到,再到methodLists查找;
  3. 如果没有在class找到,再到super_class查找;
  4. 一旦找到message这个方法,就执行它实现的IMP。

方法解析与消息转发

上面我们提到, 如果到最后都找不到, 就会来到消息转发,消息转发的流程如下:

  1. 动态方法解析 : 先问接收者所属的类, 你看能不能动态添加个方法来处理这个”未知的消息”? 如果能, 则消息转发结束.         动态处理
  2. 备胎(后备接收者/重定向) : 请接收者看看有没有其他对象能处理这条消息? 如果有, 则把消息转给那个对象, 消息转发结束.          重定向
  3. 消息签名 : 这里会要求你返回一个消息签名, 如果返回nil, 则消息转发结束.                重写转发逻辑
  4. 完整的消息转发 : 备胎都搞不定了, 那就只能把该消息相关的所有细节都封装到一个NSInvocation对象, 再问接收者一次, 快想办法把这个搞定了. 到了这个地步如果还无法处理, 消息转发机制也无能为力了。

RunTime笔记

总结:当消息转发的时候找不到方法,就先进行可否进行动态添加方法(resolveInstanceMethod:),如果返回NO的时候,就进行转发备用方法(forwardingTargetForSelector:进行),如果备用方法没用的时候,所有细节都封装到一个NSInvocation对象(methodSignatureForSelector:获取方法签名,forwardInvocation:用于返回方法选择器), 再问接收者一次。如果methodSignatureForSelector:和forwardInvocation:有一个失败的时候就会崩溃。

上面这4个方法均是模板方法,开发者可以override,由runtime来调用。最常见的实现消息转发,就是重写方法3和4,忽略这个消息或者代理给其他对象.

EX:

TTClass.h

#import <Foundation/Foundation.h>


@interface TTClass : NSObject


- (void)showData;


@end


TTClass.m

#import "TTClass.h"

#import <objc/runtime.h>

#import "TTOtherClass.h"


@implementation TTClass


//+ (BOOL)resolveInstanceMethod:(SEL)sel

//{

//    NSLog(@" >> Instance resolving %@", NSStringFromSelector(sel));

//

//    if (sel == @selector(showData)) {

//        class_addMethod([TTClass class], sel, class_getMethodImplementation([TTClass class], @selector(shows)), "[email protected]:");

//    }

//

//    return NO;

//}

//

//- (void)shows {

//    NSLog(@"%s Hello world----!", __func__);

//}


- (id)forwardingTargetForSelector:(SEL)aSelector {

    if(aSelector == @selector(showData)){

        return [TTOtherClass new];

    }

    return [super forwardingTargetForSelector:aSelector];

}


@end


TTOtherClass.h

#import <Foundation/Foundation.h>


@interface TTOtherClass : NSObject


- (void)showData;


@end


TTOtherClass.m

#import "TTOtherClass.h"


@implementation TTOtherClass


- (void)showData {

    NSLog(@"%s showData Hello world----!", __func__);

}


@end


Associated Objects

Category对某个类进行扩展属性,可以使用Associated Objects进先行处理

常用的几个方法:

void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )

id objc_getAssociatedObject (id object, const void *key )

void objc_removeAssociatedObjects (id object )


二、常用方法

(1)NSObject 的方法

class方法返回对象的类

isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);

respondsToSelector: 检查对象能否响应指定的消息

conformsToProtocol:检查对象是否实现了指定协议类的方法

methodForSelector: 返回指定方法实现的地址

(2)runtime 库函数

//获取类

Class PersonClass = object_getClass([Person class]);    

//获取方法选择器

SEL oriSEL = @selector(test1); 

//获取方法名字

Method oriMethod = Method class_getClassMethod(Class cls , SEL name); 

Method class_getInstanceMethod(Class cls , SEL name);

//添加方法

OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

    EX:class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "[email protected]:@");

Type Encodings

//替换原方法实现

class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

//交换两个方法:

method_exchangeImplementations(oriMethod, cusMethod);

//获取一个类的属性列表(返回值是一个数组)

class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

    EX:objc_property_t *propertyList = class_copyPropertyList([self class], &count);

//获取一个类的方法列表(返回值是一个数组)

class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 

    EX:Method *methodList = class_copyMethodList([self class], &count);

//获取一个类的成员变量列表(返回值是一个数组)

OBJC_EXPORT Ivar _Nonnull * _Nullable

class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 

       EX:Ivar *ivarList = class_copyIvarList([self class], &count);

//获取成员变量的名字

OBJC_EXPORT const char * _Nullable ivar_getName(Ivar _Nonnull v) 

//获取成员变量的类型

const char *ivar_getTypeEndcoding(Ivar v)

//获取一个类的协议列表(返回值是一个数组)

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);


EX:(案例来自:https://blog.csdn.net/coyote1994/article/details/52469954

1.动态添加一个类

// 创建一个类(size_t extraBytes该参数通常指定为0, 该参数是分配给类和元类对象尾部的索引ivars的字节数。)

Class clazz = objc_allocateClassPair([NSObject class], "GoodPerson", 0);

// 添加ivar// @encode(aType) : 返回该类型的C字符串

class_addIvar(clazz, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));

class_addIvar(clazz, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));

// 注册该类

objc_registerClassPair(clazz);

// 创建实例对象

id object = [[clazz alloc] init];

// 设置ivar

[object setValue:@"Tracy" forKey:@"name"];

Ivar ageIvar = class_getInstanceVariable(clazz, "_age");

object_setIvar(object, ageIvar, @18);

// 打印对象的类和内存地址

NSLog(@"%@", object);

// 打印对象的属性值

NSLog(@"name = %@, age = %@", [object valueForKey:@"name"], object_getIvar(object, ageIvar));

// 当类或者它的子类的实例还存在,则不能调用objc_disposeClassPair方法

object = nil;

// 销毁类

objc_disposeClassPair(clazz);

2.动态添加方法

TTClass *ttClass = [TTClass new];

[ttClass showData];

class_addMethod([ttClass class], @selector(ss), class_getMethodImplementation([self class], @selector(ss)), "[email protected]:");

[ttClass performSelector:@selector(ss)];


3.打印一个类的所有ivar, property 和 method(简单直接的使用)

Person *p = [[Person alloc] init];

[p setValue:@"Kobe" forKey:@"name"];

[p setValue:@18 forKey:@"age"];

//    p.address = @"广州大学城";

p.weight = 110.0f;


// 1.打印所有ivars

unsigned int ivarCount = 0;

// 用一个字典装ivarName和value

NSMutableDictionary *ivarDict = [NSMutableDictionary dictionary];

Ivar *ivarList = class_copyIvarList([p class], &ivarCount);

for(int i = 0; i < ivarCount; i++){

    NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];

    id value = [p valueForKey:ivarName];

    

    if (value) {

        ivarDict[ivarName] = value;

    } else {

        ivarDict[ivarName] = @"值为nil";

    }

}

// 打印ivar

for (NSString *ivarName in ivarDict.allKeys) {

    NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarDict[ivarName]);

}


// 2.打印所有properties

unsigned int propertyCount = 0;

// 用一个字典装propertyName和value

NSMutableDictionary *propertyDict = [NSMutableDictionary dictionary];

objc_property_t *propertyList = class_copyPropertyList([p class], &propertyCount);

for(int j = 0; j < propertyCount; j++){

    NSString *propertyName = [NSString stringWithUTF8String:property_getName(propertyList[j])];

    id value = [p valueForKey:propertyName];

    

    if (value) {

        propertyDict[propertyName] = value;

    } else {

        propertyDict[propertyName] = @"值为nil";

    }

}

// 打印property

for (NSString *propertyName in propertyDict.allKeys) {

    NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyDict[propertyName]);

}


// 3.打印所有methods

unsigned int methodCount = 0;

// 用一个字典装methodName和arguments

NSMutableDictionary *methodDict = [NSMutableDictionary dictionary];

Method *methodList = class_copyMethodList([p class], &methodCount);

for(int k = 0; k < methodCount; k++){

    SEL methodSel = method_getName(methodList[k]);

    NSString *methodName = [NSString stringWithUTF8String:sel_getName(methodSel)];

    

    unsigned int argumentNums = method_getNumberOfArguments(methodList[k]);

    

    methodDict[methodName] = @(argumentNums - 2); // -2的原因是每个方法内部都有self 和 selector 两个参数

}

// 打印method

for (NSString *methodName in methodDict.allKeys) {

    NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodDict[methodName]);

}


3.交换方法的实现

// 获取两个类的类方法

Method m1 = class_getClassMethod([Person class], @selector(run));

Method m2 = class_getClassMethod([Person class], @selector(study));

// 开始交换方法实现

method_exchangeImplementations(m1, m2);

// 交换后,先打印学习,再打印跑!

[Person run];

[Person study];


IOS技术交流群:129582174