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发送消息逻辑:
- 首先根据receiver对象的isa指针获取它对应的class;
- 优先在class的cache查找message方法,如果找不到,再到methodLists查找;
- 如果没有在class找到,再到super_class查找;
- 一旦找到message这个方法,就执行它实现的IMP。
方法解析与消息转发
上面我们提到, 如果到最后都找不到, 就会来到消息转发,消息转发的流程如下:
- 动态方法解析 : 先问接收者所属的类, 你看能不能动态添加个方法来处理这个”未知的消息”? 如果能, 则消息转发结束. 动态处理
- 备胎(后备接收者/重定向) : 请接收者看看有没有其他对象能处理这条消息? 如果有, 则把消息转给那个对象, 消息转发结束. 重定向
- 消息签名 : 这里会要求你返回一个消息签名, 如果返回nil, 则消息转发结束. 重写转发逻辑
- 完整的消息转发 : 备胎都搞不定了, 那就只能把该消息相关的所有细节都封装到一个NSInvocation对象, 再问接收者一次, 快想办法把这个搞定了. 到了这个地步如果还无法处理, 消息转发机制也无能为力了。
总结:当消息转发的时候找不到方法,就先进行可否进行动态添加方法(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)
//获取一个类的方法列表(返回值是一个数组)
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.****.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