美文网首页
runtime详解

runtime详解

作者: 陌巷先森 | 来源:发表于2019-02-28 10:08 被阅读0次

一、runtime简介

Objective-C是一门动态语言,它的一切都是基于runtime实现的。对于Objective-C来说,这个运行时系统就像一个操作系统一样,让所有的工作都能正常运行,在程序运行中,Objective-C最终都转换成了runtime的C语言代码。runtime是一套底层的纯C语言的API,属于1个C语言库,包含了很多的C语言API。runtimeAPI的实现是用C++开发的,是一套苹果开源的框架。
在runtime中,对象可以用C语言中的结构体表示,而方法可以用C语言实现,另外加上一些额外特性,这些结构体和函数被runtime函数封装后,让Objective-C可以面向对象编程。

二、runtime消息传递

Objective-C中的方法调用并不是直接进行调用方法,而是转换为消息传递。其大致过程如下:

  • 使用[receiver message]语法时,编译器会转成消息发送objc_msgSend(receiver,message)
  • receiver会通过isa指针找到其所属的类,然后在cache或者methodLists中查找message(类方法查找元类),找到就去执行它的实现IMP
  • 如果在类中没有找到该方法,则通过super_class往上一级父类查找(如果一直找到NSObject都没有,就进行消息转发)

objc_msgSend的方法定义如下:

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

该函数有两个参数,一个 id 类型,一个 SEL 类型

id(obj_object)

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

我们通常说的对象就是这个样子,这个对象被定义在<objc/objc.h>中,其只有一个isa成员变量,对象可以通过isa指针找到其所属的类

SEL(objc_selector)

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL也被定义在<objc/objc.h>中,它是个映射到方法的C字符串,你可以用 Objective-C 编译器命令 @selector() 或者 runtime 系统的 sel_registerName 函数来获得一个 SEL 类型的方法选择器。

IMP

/// A pointer to the function of a method implementation. 指向一个方法实现的指针
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP也被定义在<objc/objc.h>中,IMP本质上就是一个函数指针,指向方法的实现,当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

三、runtime中用到的概念

<objc/runtime.h>中对一个类进行了完全的分解,将类定义的每一个部分都抽象为一个type,用结构体来表示。

/// An opaque type that represents a method in a class definition.描述类中的一个方法
typedef struct objc_method *Method;
/// An opaque type that represents an instance variable.实例变量
typedef struct objc_ivar *Ivar;
/// An opaque type that represents a category.类别Category
typedef struct objc_category *Category;
/// An opaque type that represents an Objective-C declared property.类中声明的属性
typedef struct objc_property *objc_property_t;
/// 类在runtime中的表示,实例的isa指向类对象,类对象的isa指向于元类
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
} ;

下面来仔细分析一下每一个的结构体

Class(objc_class)

struct objc_class {
    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;
} OBJC2_UNAVAILABLE;

objc_class结构体中定义了很多变量

  • super_class :指向其父类
  • name:类名
  • version:类的版本信息
  • info:类的详情
  • instance_size:类的实例大小
  • ivars:指向该类的成员变量列表
  • methodLists:指向该类的实例方法列表,它将方法选择器和方法实现地址联系起来
  • cache:runtime系统中会把被调用的方法缓存到cache中,下次查找时效率更高
  • protocols:指向该类的协议列表

Method(objc_method)

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}OBJC2_UNAVAILABLE;
  • method_name:方法名
  • method_types:方法类型
  • method_imp:方法实现
    在这个结构体中,我们已经看到了SEL和IMP,说明SEL和IMP其实都是Method的属性。

Category(objc_category)

struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}OBJC2_UNAVAILABLE;
  • category_name:分类的名字
  • class_name:类的名字
  • instance_methods:category中所有给类添加的实例方法的列表
  • class_methods:category中所有给类添加的类方法的列表
  • protocols:category实现的所有协议的列表

ivar(objc_ivar)

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
  • ivar_name:成员变量名
  • ivar_type:成员变量类型

四、runtime应用

对象关联

分类不能添加属性,但是通过关联对象可以实现为分类添加属性

//关联对象
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)
  • id object:被关联的对象
  • const void *key:关联的key,要求唯一
  • id value:关联的对象
  • objc_AssociationPolicy policy:内存管理的策略
//使用案例
@interface NSObject (Category)

@property(nonatomic, copy) NSString *name;

@end

@implementation NSObject (Category)


- (void)setName:(NSString *)name{
    //关联对象
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    //获取关联的值
    return objc_getAssociatedObject(self, @"name");
}
@end

方法交换

runtime系统提供了函数用于动态替换类方法和实例方法
method_exchangeImplementations交换两个方法的实现

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method workMethod = class_getInstanceMethod(self, @selector(work));
        Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
        
        method_exchangeImplementations(workMethod, sleepMethod);
    });
}

- (void)work{
    NSLog(@"the person is working");
}

- (void)sleep{
    NSLog(@"the person is sleeping");
}

动态加载方法

当调用一个未实现的方法,或者说发送未知的消息给接收者时候,消息的接受者会调用resolveInstanceMethod

// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
    NSLog(@"the person is eating");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 动态添加eat方法
        
        // 第一个参数:给哪个类添加方法
        // 第二个参数:添加方法的方法编号
        // 第三个参数:添加方法的函数实现(函数地址)
        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}

自动归档

用runtime提供的函数遍历model的属性,并对属性进行encode和decode操作

- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int outCount;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
    free(ivars);

}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int outCount;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
        free(ivars);
    }
    return self;
}

字典转模型

遍历自身对象的所有属性,如果属性在字典中有对应的值,则将其赋值
+ (instancetype)modelWithDict:(NSDictionary *)dict{
    id model = [[self alloc] init];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i++) {
        Ivar ivar = ivars[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //截取掉变量前的_
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];
        [model setValue:value forKeyPath:ivarName];
    }
    free(ivars);
    return model;
}

参考:完整项目资料下载

相关文章

网友评论

      本文标题:runtime详解

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