前言:OC是一门动态性比较强的语言,它的动态性就是由Runtime支撑和实现的。本文先介绍了Runtime的概念,然后详细地介绍了OC的消息转发机制,最后介绍了几种Runtime常见的使用场景,并配上具体的代码说明。
一、Runtime概念:
1、Runtime概念:
Runtime是一套C语言的API,封装了很多动态性相关的函数。OC是一门动态性比较强的语言,允许很多操作推迟到程序运行时再进行,OC的动态性就是由Runtime支撑和实现的,平时编写的OC代码,底层都是转换成Runtime API进行调用。
2、OC的消息机制:
OC中的方法调用其实都是转化成了objc_msgSend函数调用,给receiver(方法调用者)发送一条消息(selector方法名),objc_msgSend有三个阶段:消息发送阶段(当前类、父类中查找)、动态方法解析阶段、消息转发阶段。
二、探寻消息转发机制:
1、OC中方法调用过程:
如果需要了解OC对象的内部结构,请点击OC对象的本质。
一个普通的对象方法[objc method],编译时转成消息发送objc_msgSend(objc, method),Runtime执行时流程如下:
1)通过objc的ISA指针找到它的class;
2)在它的class里methods找到method方法;
3)如果它的class没有该method,那么就去父类中查找;
4)一旦找到该method,那么就执行它的实现IMP;
5)如果在父类中还找不到,那么进入动态方法解析+(BOOL)resolveInstanceMethod:(SEL)sel(经测试,如果直接返回YES或者NO都会执行后续的forward方法);
6)如果动态解析没有添加方法,那么进入到消息转发阶段-(id)forwardingTargetForSelector:(SEL)aSelector;
7)如果消息转发的对象还找不到方法,就抛出经典异常unrecognized selector,找不到该方法;
在第一步objc通过ISA指针找到它的class之后,严格来说先从它的class中的缓存中查询方法method,如果没有则继续在methods方法列表进行查询,后续操作一致。
代码验证如下:创建一个Dog类
@interface Dog : NSObject
- (void)wangWang; //正常调用
- (void)wangWangResolve; //方法解析
- (void)wangWangForward_YES; //消息转发YES
- (void)wangWangForward_NO; //消息转发NO
- (void)wangWangForward_All; //完整的消息转发
@end
// .m文件
#import "Dog.h"
#import "Cat.h"
#import <objc/runtime.h>
@implementation Dog
// 直接实现方法
- (void)wangWang {
NSLog(@"dog wangWang");
}
// 动态解析实例方法
//+ (BOOL)resolveClassMethod:(SEL)sel //解析类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(wangWangResolve))
{
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// C语言函数
void dynamicMethodIMP(id self, SEL _cmd)
{
NSLog(@"dog wangWangResolve");
}
// 转发备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(wangWangForward_YES)) {
return [Cat new];
}
return [super forwardingTargetForSelector:aSelector];
}
//签名,进入forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(wangWangForward_All)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Cat *cat = [Cat new];
if([cat respondsToSelector:sel]) {
[anInvocation invokeWithTarget:cat];
} else {
[self doesNotRecognizeSelector:sel];
}
}
@end
创建一个Cat类:
@interface Cat : NSObject
- (void)wangWangForward_YES; //消息转发YES
- (void)wangWangForward_All; //完整的消息转发
@end
// .m文件
@implementation Cat
- (void)wangWangForward_YES {
NSLog(@"cat wangWangForward_YES");
}
- (void)wangWangForward_All {
NSLog(@"cat wangWangForward_All");
}
@end
分别运行注释代码:
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [Dog new];
[dog wangWang];
// [dog wangWangResolve];
// [dog wangWangForward_YES];
// [dog wangWangForward_NO];
// [dog wangWangForward_All];
}
打印结果:
// [dog wangWang] 正常在.m文件实现方法
dog wangWang
// [dog wangWangResolve] 动态解析添加方法
dog wangWangResolve
// [dog wangWangForward_YES] 成功消息转发,对象是cat
cat wangWangForward_YES
// [dog wangWangForward_NO] 前面阶段都找不到方法,抛出异常
-[Dog wangWangForward_NO]: unrecognized selector sent to instance 0x6000031ec350
// [dog wangWangForward_All] 完整的消息转发
cat wangWangForward_All
2、完整的消息转发:
如果forwardingTargetForSelector这一步还不能处理消息,那唯一能做的就是启用完整的消息转发机制了。首先发送一个methodSignatureForSelector消息,如果有签名返回,则进入到forwardInvocation,在该方法里进行最后的消息转发,否则直接发送doesNotRecognizeSelector消息,程序抛出异常。
至于“v@:”具体的意思,可以查看官方文档Type Encodings。
三、Runtime应用:
Runtime应用场景非常多,这里介绍一些项目中常用的场景:
1)关联对象(AssociateObject)给分类添加的属性实现setter和getter方法,运行中方法替换(method_exchangeIMP),传送门-Category分类;
2)KVO的底层实现,通过runtime动态创建一个派生类对象,传送门-KVC & KVO;
3)实现字典和模型的自动转换(MJExtension),通过runtime遍历Model的所有属性,把服务器返回的JSON格式转成字典,然后通过KVC给Model赋值;
4)实现NSCoding的自动归档和解档,通过runtime遍历Model的所有属性,并对属性进行encode和decode操作;
5)其它runtime的API调用;
1、字典和模型的自动转换:
对NSObject写一个分类,添加一个initWithDict:(NSDictionary)dict分类,如下:
@interface NSObject (DictToModel)
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
// .m
@implementation NSObject (DictToModel)
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
// 获取类的属性及属性对应的类型
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString *propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
// 立即释放properties指向的内存
free(properties);
// 根据类型给属性赋值
for (NSString *key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
@end
创建一个Person类:
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end
运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dict = @{@"name": @"路飞",
@"age": @(15)
};
Person *p = [[Person alloc] initWithDict:dict];
NSLog(@"name = %@, age = %ld", p.name, p.age);
}
打印结果:
name = 路飞, age = 15
2、NSCoding的自动归档和解档:
在Model的基类中重写方法:
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end
// .m文件
@implementation Person
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i]; //拿到Ivar
const char *name = ivar_getName(ivar); //获取到属性的C字符串名称
NSString *key = [NSString stringWithUTF8String:name];
//解档
id value = [coder decodeObjectForKey:key];
// 利用KVC赋值
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
//告诉系统归档的属性是哪些
unsigned int count = 0; //表示对象的属性个数
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
//归档 -- 利用KVC
[coder encodeObject:[self valueForKey:key] forKey:key];
}
free(ivars);//在OC中使用了Copy、Creat、New类型的函数,需要释放指针!!(注:ARC管不了C函数)
}
@end
运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dict = @{@"name": @"路飞",
@"age": @(15)
};
Person *p = [[Person alloc] initWithDict:dict];
NSString *temp = NSTemporaryDirectory();
NSString *filePath = [temp stringByAppendingPathComponent:@"archive.text"]; //注:保存文件的扩展名可以任意取,不影响。
//NSLog(@"%@", filePath);
// 归档
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
// 解档
Person *p1 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"name = %@, age = %ld", p1.name, p1.age);
}
打印结果:
name = 路飞, age = 15
3、其它runtime的API调用:
1)[super class]的底层调用,都是通过object_getClass(self)进行调用,代码如下:
PS:正常情况下的super与self比较,super是从父类开始查找方法,self是从本类开始;
// 创建一个Person类
@interface Person : NSObject
@end
@implementation Person
@end
// 创建一个Student类继承自Person
@interface Student : Person
@end
@implementation Student
- (id)init {
if (self = [super init]) {
Class cls1 = [self class]; //调用object_getClass(self)
Class cls2 = [self superclass];
Class cls3 = [super class]; //给当前接受者发送消息,当前接受者就是self
Class cls4 = [super superclass];
NSLog(@"cls1 = %@, cls2 = %@, cls3 = %@, cls4 = %@", cls1, cls2, cls3, cls4);
}
return self;
}
@end
运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [Student new];
}
打印结果:
cls1 = Student, cls2 = Person, cls3 = Student, cls4 = Person
在objc4源码中的NSObject.mm文件中,class的实现:
- (Class)class {
return object_getClass(self);
}
- (Class)superclass {
return [self class]->superclass;
}
2)isMemberOfClass与isKindOfClass的区别:
-(BOOL)isMemberOfClass:(Class)cls:调用者是否是cls本类的实例;
-(BOOL)isKindOfClass:(Class)cls:调用者是否是cls本类的实例或者子类的实例;
查看objc4源码,很容易理解:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls; //是否是本类
}
- (BOOL)isKindOfClass:(Class)cls {
// self是否是本类或者子类
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
代码验证,运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [Student new];
BOOL isMember = [stu isMemberOfClass:[Person class]];
BOOL isKind = [stu isKindOfClass:[Person class]];
BOOL instance_isMember = [stu isMemberOfClass:[Student class]];
BOOL class_isMember = [Student isMemberOfClass:[Student class]];
NSLog(@"isMember = %d, isKind = %d", isMember, isKind);
NSLog(@"instance_isMember = %d, class_isMember = %d", instance_isMember, class_isMember);
}
打印结果:
isMember = 0, isKind = 1
// [Student isMemberOfClass:[Student class]] 表示Student的元类与Student类比较,结果是false
instance_isMember = 1, class_isMember = 0
觉得写的不错,有些启发或帮助,点个赞哦!