runtime简介
Runtime就是运行时, 核心就是消息机制. 对OC的函数调用,是一个动态调用过程,只有在运行的时候runtime系统才能知道真正调用的哪一个函数(C语言在函数调用过程中, 编译时候就已经决定会调用哪个函数了).
iOS Runtime中实例对象和类的本质
实例对象的本质
OC是一门面向对象的编程语言,在编译过程中,编译器会将OC对象转化成结构体.
在objc.h中找到:
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到我们常用的Class, id等关键字的定义.
OC中实际的类Class, 会被编译成struct objc_class. 我们操作的类的对象实例是struct objc_object, 并且该结构体中有一个指针指向struct objc_class.
iOS OC中类的本质
OC对象的结构体中有一个Class指针能够理解, 因为要知道该对象是哪个类的对象.但是我们在objc-runtime-new.h中发现objc_class继承自objc_object的.
struct objc_class : objc_object {
// Class ISA; // 继承了
Class superclass;
...
在runtime.h中, 我们看到OC类的结构体struct objc_class的具体定义
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
OC中类Class中也有一个指针指向Class, 因此类Class本质上也是一个对象, 我们一般称为类对象, 这个指向的Class是就是元类(metaClass)的对象.
当我们调用对象方法时候, 会通过对象中的Class指针找到对应的Class,然后调用实例方法,同理当我们调用类方法时候, 会通过Class中的Class指针找到对应的meta Class,然后调用meta Class中的方法.
OC一般会隐藏元类, 并且元类也是某个类的实例, 这个类我们一般称为根元类(root meta Class). 并且所有的元类的根元类都是一个, 并且根元类的元类是它自己. (实际中根元类是NSObject的元类)
NSString实例的isa指针链:
image
iOS 中OC方法调用的本质
OC中的方法调用称为消息发送, 具体格式是[receiver message].例如:
NSMutableString *str = [[NSMutableString alloc] initWithString:@"hello"];
[str appendString:@" world"];
其中str就是receiver, appendString:就是message.
在message.h头文件中如下方法,这个方法是runtime的核心方法,
void objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend(receiever, selector, arg1, arg2, ...)
调用实例如下:
objc_msgSend(str, @selector(appendString:), @" world");
该消息方法为消息的动态绑定完成了以下工作:
- 它会主动查找receiver的selector对应的方法实现IMP
- 然后将参数传递给receiver object, 然后调用这IMP
- 最后返回该方法的返回值
==IMP:一个函数指针,保存了方法的地址==
为了使得objc_msgSend能完成通过selector查找receiver对应的IMP, 我们知道一个OC类和OC对象会有一个isa指针,指向他们各自的Class, 同时OC类还有一个super指针指向父类.
具体过程就是通过isa指针找到对应的class struct, 然后在dispatch table里面查找selector对应的方法, 如果没有找到,那么通过super指针查找父类的dispatch table, 一直找下去, 直到NSObject类, 如果还没有找到,就调用NSObject的doesNotRecognizeSelector:方法, 然后报unrecognized selector错误.
iOS runtime实战应用
1.iOS runtime 进行添加属性,并支持KVO监听
iOS 中category和runtime的AssociatedObject是两大非常重要的工具:
category可以给既有类直接添加方法
associateObject可以给既有类添加属性(类似成员变量)
结合这两个工具, 那么通过category添加property方法.然后结合associateObject增加关联对象,完成属性存取.
==需要加入头文件#import <objc/runtime.h>==
@interface UIViewController (Extension)
@property (nonatomic, copy) NSString * categoryString;
@end
@implementation UIViewController (Extension)
-(NSString *)categoryString{
return objc_getAssociatedObject(self, @selector(categoryString));
}
-(void)setCategoryString:(NSString *)categoryString{
objc_setAssociatedObject(self, @selector(categoryString), categoryString, OBJC_ASSOCIATION_COPY);
}
@end
并且这种方法也支持KVO的监听:
-(void)test{
self.categoryString = @"Runtime生成的属性";
[self addObserver:self forKeyPath:@"categoryString" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"<接收到通知: object:%@ keyPath:%@ change:%@>", object, keyPath,change);
}
2.(1)交换两个方法的实现,(2)拦截系统自带的方法调用功能
Method class_getClassMethod(Class cls , SEL name) //获得某个类的类方法
Method class_getInstanceMethod(Class cls , SEL name)//获得某个类的实例对象方法
void method_exchangeImplementations(Method m1 , Method m2)//交换两个方法的实现
案例1:方法简单的交换
创建一个Person类,类中实现以下两个类方法,并在.h 文件中声明
+ (void)game {
NSLog(@"游戏");
}
+ (void)study {
NSLog(@"学习");
}
[Person study];
[Person game];
下面通过runtime 实现方法交换,类方法用class_getClassMethod ,对象方法用class_getInstanceMethod
// 获取两个类的类方法
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
// 交换后
[Person study];
[Person game];
控制台打印
2018-04-26 15:20:37.224745+0800 runtime-test[15020:7430831] 学习
2018-04-26 15:20:37.224835+0800 runtime-test[15020:7430831] 游戏
2018-04-26 15:20:37.225668+0800 runtime-test[15020:7430831] 游戏
2018-04-26 15:20:37.225720+0800 runtime-test[15020:7430831] 学习
案例2:拦截系统方法
1、为UIImage建一个分类(UIImage+Category)
2、在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,添加自己的逻辑判断
+ (UIImage *)xxx_imageNamed:(NSString *)name {
double version = 11.11;
if (version == 11.11) {
name = [name stringByAppendingString:@"5.1_time"];
}
return [UIImage xxx_imageNamed:name];
}
3、分类中重写UIImage的load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)
+ (void)load {
// 获取两个类的类方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(xxx_imageNamed:));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
}
==注意:自定义方法中最后一定要再调用一下系统的方法,让其有加载图片的功能,但是由于方法交换,系统的方法名已经变成了我们自定义的方法名(就是用我们的名字能调用系统的方法,用系统的名字能调用我们的方法),这就实现了系统方法的拦截!==
3.获得一个类的成员变量( Ivar )、属性( Property )、方法( Method )、协议( Protocol )
获得某个类的所有成员变量(outCount 会返回成员变量的总数)
参数:
1、哪个类
2、放一个接收值的地址,用来存放属性的个数
3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
获得成员变量的名字
const char *ivar_getName(Ivar v)
获得成员变量的类型
const char *ivar_getTypeEndcoding(Ivar v)
案例1:获取Person类中所有成员变量的名字和类型
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
// 遍历所有成员变量
for (int i = 0; i < outCount; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"成员变量名:%s 成员变量类型:%s",name,type);
}
// 注意释放内存!
free(ivars);
==同样:==
// 测试 打印属性列表
- (void)testPrintPropertyList {
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property----="">%@", [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
// 测试 打印方法列表
- (void)testPrintMethodList {
unsigned int count;
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method----="">%@", NSStringFromSelector(method_getName(method)));
}
free(methodList);
}
// 测试 打印协议列表
- (void)testPrintProtocolList {
unsigned int count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolName]);
}
free(protocolList);
}
案例2:利用Runtime进行 json/dict -> model
-(instancetype)initWithNSDictionary:(NSDictionary *)dict{
self = [super init];
if (self) {
[self processDict:dict];
}
return self;
}
-(void)processDict:(NSDictionary *)dict{
NSMutableArray *keys = [[NSMutableArray alloc] init];
unsigned int count = 0;
objc_property_t *props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t prop = props[i];
const char *propCStr = property_getName(prop);
NSString *propName = [NSString stringWithCString:propCStr encoding:NSUTF8StringEncoding];
[keys addObject:propName];
}
free(props);
for (NSString *key in keys) {
if ([dict valueForKey:key]) {
[self setValue:[dict valueForKey:key] forKey:key];
}
}
}
案例3:利用runtime 获取所有属性来重写归档解档方法
// 解档方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
// 获取所有成员变量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
// 将每个成员变量名转换为NSString对象类型
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 忽略不需要解档的属性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
// 根据变量名解档取值,无论是什么类型
id value = [aDecoder decodeObjectForKey:key];
// 取出的值再设置给属性
[self setValue:value forKey:key];
// 这两步就相当于以前的 self.age = [aDecoder decodeObjectForKey:@"_age"];
}
free(ivars);
return self;
}
// 归档调用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 获取所有成员变量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
// 将每个成员变量名转换为NSString对象类型
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 忽略不需要归档的属性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
// 通过成员变量名,取出成员变量的值
id value = [self valueForKeyPath:key];
// 再将值归档
[aCoder encodeObject:value forKey:key];
// 这两步就相当于 [aCoder encodeObject:@(self.age) forKey:@"_age"];
}
free(ivars);
}
网友评论