美文网首页
iOS Runtime

iOS Runtime

作者: 阿饼six | 来源:发表于2019-10-10 10:35 被阅读0次

​ 前言: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 

觉得写的不错,有些启发或帮助,点个赞哦!

相关文章

网友评论

      本文标题:iOS Runtime

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