美文网首页
Runtime底层学习

Runtime底层学习

作者: 朝夕向背 | 来源:发表于2018-11-16 15:57 被阅读0次

Objective-C作为一门高级编程语言,想要成为可执行文件需要先编译成汇编语言,在汇编成机器语言,机器语言也是计算机能识别的唯一语言。但是Objective-C并不能直接编译成汇编语言,需要先转写为C语言在进行编译和汇编的操作。从Objective-C到C语言的过渡就是由Runtime来实现的。
Objective-C的语言特性是动态性比较强,这种动态性就是由Runtime API来支撑的。想要了解Runtime的原理,首先需要知道OC语言的isaCache缓存class_rw_t

isa详解

要想学习Runtime,首先要了解它底层的一些常用的数据结构,比如isa指针。
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。共用体如下

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };

我们来看一段代码

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,  assign, getter=isTall) BOOL tall;
@property (nonatomic,  assign, getter=isRich) BOOL rich;
@property (nonatomic,  assign, getter=isHandsome) BOOL handsome;
@end

#import "Person.h"

@implementation Person

@end

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.rich = NO;
        person.tall = YES;
        person.handsome = NO;
        NSLog(@"%zd",class_getInstanceSize([Person class]));
    }
    return 0;
}

在main.m函数中,系统给Person对象分配了16个字节的内存,其中Person的isa指针占用8个字节,三个属性占用3个字节。
既然Person的属性是BOOL类型,可以考虑使用共用体。

共用体:共用体内的成员变量共用一块内存。

Person类的.m文件内,可以修改成如下:

#import "Person.h"

@interface Person()
{
    union{
        char bits;
        struct{
            char tall : 1;
            char rich : 1;
            char handsome  : 1;
        };
    }_tallRichHandsome;
}
@end

@implementation Person

- (void)setTall:(BOOL)tall{
    _tallRichHandsome.bits = tall;
}
- (BOOL)isTall{
    return !!(_tallRichHandsome.bits );
}

- (void)setRich:(BOOL)rich{
    _tallRichHandsome.bits = rich;
}
- (BOOL)isRich{
    return !!(_tallRichHandsome.bits);
}

- (void)setHandsome:(BOOL)handsome{
    _tallRichHandsome.bits = handsome;
}
- (BOOL)isHandsome{
    return !!( _tallRichHandsome.bits);
}
@end

在结构体union中

       struct{
            char tall : 1;
            char rich : 1;
            char handsome  : 1;
        };

只是为了方便阅读代码,struct内的成员变量,都存储在unionchar中。所以回到开头的union isa_t中,struct是为了阅读方便,共用体union中的信息都存储在uintptr_t bits;中。

Class的结构

objc_class的结构
  • class_rw_t里面的methodspropertiesprotocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
    class_rw_t方法结构图
    method_array_t是一个二维数组,每一个元素是一个分类或者类的方法数组method_list_t。在method_list_t数组中包含的才是分类或者类的方法信息。
  • class_ro_t里面的baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的,包含了类的初始内容。
    class_ro_t结构图
  • method_t是对方法\函数的封装
    method_t
  • IMP代表函数的具体实现
    IMP
  • SEL代表方法\函数名,一般叫做选择器。底层结构跟char *类似
    可以通过@selector()sel_registerName()获得。

方法缓存

Class内部结构中有个方法缓存(cache_t),用散列表来缓存曾经调用的方法,可以提高方法的查找速度。

cache_t
buckets是散列表,是数组。
bucket_t
  • 散列表的存储方式
    散列表的存储方式
    在上图中,当Person类中有方法- (void)personTest;
  • 首先,通过@selector(personTest)&_Mask,得出一个值,比如得到2,则将这个方法personTest存储到下表为2的数组中。一般存储的位置从高到低。如果当前被存储内容,这得到数值减1,往下存储。
  • 当调用这个方法时,通过@selector(personTest)&_Mask,得到同样的值,然后直接找到这个方法的地址值,直接调用。如果取值时,当前的cache_key_t不是所需要的,这得到数值减1,往下取值。
  • 缺点:用空间换时间。

objc_msgSend

先来看段代码

#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)personTest;
@end

#import "Person.h"

@implementation Person
- (void)personTest{

}
@end

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
       [person personTest];
    }
    return 0;
}

当调用- (void)personTest;方法时,是在底层转化为C语言调用

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
    }
    return 0;
}
  • OC中的方法调用,其实都是转化为objc_msgSend函数的调用,给receiver(方法接收者)发送了一条消息(selector方法名);
  • objc_msgSend的执行流程可以分为3大阶段:

✔️ 消息发送

  • 1、判断消息接收者receiver是否为nil。如果是nil,这直接退出;
  • 2、如果receiver不为nil,receiver通过isa指针找到receiverClass(接收者类对象)。从receiverClasscache中查找方法。如果找到方法,调用方法,结束查找;
  • 3、如果没有找到方法,则从reveiverClassclass_rw_t中查找方法。如果找到了方法,调用方法,结束查找,并将方法缓存到receiverClasscache中;
  • 4、如果没找到方法,从superClass的cache中查找方法,如果找到方法,调用方法,结束查找,并将方法缓存到receiverClasscache中;
  • 5、如果没有找到方法,从superClass的class_rw_t中查找方法。如果找到方法,调用方法,结束查找,并将方法缓存到receiverClasscache中;
  • 6、如果没找到方法,则判断是否还有superclass。如果有,则执行第4步。如果没找到方法,则动态方法解析。

✔️动态方法解析

  • 是否曾经存在过动态方法解析,如果是,就进入消息转发阶段。
  • 如果没有动态方法解析过,则调用+ (BOOL)resolveInstanceMethod:(SEL)sel方法(实例方法调用)或者+(BOOL)resolveClassMethod:(SEL)sel(类方法调用)判断是否有动态绑定方法,并标记为已经动态方法解析,然后进入消息发送阶段。

✔️消息转发

  • 通过动态方法解析不成功,则调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,把这条消息转给其他接收者来处理,如果有其他接收者来处理,返回不为nil,则调用objc_msgSend(返回值,SEL)
  • 如果返回值为nil,则调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,生成方法签名,然后系统用这个方法签名生成NSInvocation对象,如果这个方法返回的nil,则崩溃报错unrecognized selector sent to instance
  • 如果- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法返回值不为nil,则调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法。

注意:
1、如果调用的是类方法,上述流程中,方法换成类方法(+开头)。
2、dynamic告诉编译器不要自动生成setter方法和getter方法的实现,等到运行时在添加方法的实现。
3、@synthesize age = _age;是为age属性生成_age,并且自动生成setter方法和getter方法,并赋值。

有关super

[super message]的底层实现

objc_msgSendSuper(self,[Person class],@selector(message));

[super class]返回的是当前类

- (class)class{
     return object_getClass(self);
}

[super superClass]返回的是父类

- (Class)superclass{
     return class_getSuperclass(object_getClass(self));
}
  • 1、消息接收者仍然是子类对象;
  • 2、从父类开始查找方法的实现;

Runtime的应用API

类相关
  • 1、动态创建一个类(参数:父类,类名,额外的内存空间)创建之后要注册这个类
    Class objc_allocateClassPair(Class superClass,const char *name,size_t extraBytes)
  • 2、注册一个类(要在类注册之前添加成员变量)
    Void objc_registerClassPair(Class cls)
  • 3、销毁一个类
    Void objc_disposeClassPair(Class cls)
  • 4、获取isa指向的Class,或者类对象(元类对象)
    Class object_getClass(id obj)
  • 5、设置isa指向的class,此isa指向其他的类
    Class object_setClass(id obj,class cls)
  • 6、判断一个OC对象是否为class,传入实例对象,BOOL为0,传入类对象,BOOL为1
    BOOL object_isClass(id obj)
  • 7、判断一个Class是否为元素
    BOOL class_isMetaClass(Class cls)
  • 8、获取父类
    Class class_getSuperclass(class cls)
成员变量相关
  • 9、获取成员变量信息(获取不到值)
    Ivar class_getInstanceVariable(Class cls,const char *name)
  • 10、拷贝实例变量列表(最后需要调用free释放)
    Ivar *class_copyIvarList(Class cls,unsigned int *outCount)
  • 11、设置和获取成员变量的值
    Void object_setIvar(id obj,Ivar ivar,id value)
    Id object_getIvar(id obj,Ivar ivar)
  • 12、动态添加成员变量
    BOOL class_addIvar(Class cls,const char *name,size_t size,unit8_t alignment,
    const char *types)
  • 13、获取成员变量的相关信息
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
属性相关
  • 14、获取一个属性
  • 15、拷贝属性列表(最后需要调用free释放)
    objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount)
  • 16、动态添加属性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t
    *attributes, unsigned int attributeCount)
  • 17、动态替换属性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t
    *attributes,unsigned int attributeCount)
  • 18、获取属性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)
方法相关
  • 19、获得一个实例方法、类方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
  • 20、方法实现相关操作
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2)
  • 21、拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)
  • 22、动态添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
  • 23、动态替换方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
  • 24、获取方法的相关信息(带有copy的需要调用free去释放)
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)
  • 25、选择器相关
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
  • 26、用block作为方法实现
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)

相关文章

  • RunTime的使用和介绍

    自己比较懒 网上搜集了下runtime相关的总结,学习学习runtime的简介runtime是一套比较底层的纯C语...

  • Runtime底层学习

    Objective-C作为一门高级编程语言,想要成为可执行文件需要先编译成汇编语言,在汇编成机器语言,机器语言也是...

  • RUNTime操作基础

    runtime是OC比较底层的东西,而我们在学OC的时候很容易忽视runtime的学习,但是runtime的熟悉还...

  • RunTime

    RunTime(运行时)是属于OC的一套底层实现,可以实现一些OC无法实现的底层操作。当初学习RunTime的时候...

  • iOS objc_msgSend 伪代码

    如题,记录下学习runtime底层一些汇编代码的解读

  • 如何精进iOS笔记

    首先学习渠道: github Google 极客时间方式: 自己学学习方向--->往底层走: runtime原理 ...

  • 2019-03-02

    Runtime Objective-C Runtime iOS底层原理探究-Runtime isa 和 Class...

  • iOS底层原理总结 - 探寻Runtime本质(三)

    本篇主要是对小码哥底层视频学习的总结。方便日后复习。上篇《iOS底层原理总结 - 探寻Runtime本质(二)》:...

  • iOS底层原理总结 - 探寻Runtime本质(四)

    本篇主要是对小码哥底层视频学习的总结。方便日后复习。上篇《iOS底层原理总结 - 探寻Runtime本质(三)》:...

  • iOS底层原理总结 - 探寻Runtime本质(二)

    本篇主要是对小码哥底层视频学习的总结。方便日后复习。上篇《iOS底层原理总结 - 探寻Runtime本质(一)》:...

网友评论

      本文标题:Runtime底层学习

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