美文网首页iOS Developer程序员
通过OC运行时解析对象、类、元类之间的关系

通过OC运行时解析对象、类、元类之间的关系

作者: lattr | 来源:发表于2018-04-22 22:00 被阅读38次

    运行时是OC底层的一套C语言的API,编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。

    在OC中类本身被称为类对象,简称类。而类对象的实例被称为实例对象,简称对象。
    注意:类对象和类的对象是两个东西。

    OC中NSObject的定义

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    
    ...
    
    

    大家都很熟悉NSObject。它遵守了<NSObject>协议,定义了一些属性和方法外,还有一个Class类型的isa指针。
    除了Class外,还有一些idSELMethodIMP等见不到底层结构的类型。但是在运行时中可以找到OC与C之间的桥梁。

    typedef struct objc_object *id;
    
    typedef struct objc_class *Class;
    
    typedef struct objc_selector *SEL;
    
    typedef struct objc_method *Method;
    
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
    #endif
    

    运行时中对象的定义

    #include <objc/objc.h>中,对象的定义为:

    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    结构体类型的对象中只有一个Class类型的isa指针。可见与OC中NSObject的定义十分类似。而Class定义为objc_class类型的结构体指针:

    //一个代表OC类的不透明类型
    typedef struct objc_class *Class;
    

    相当于给指向类objc_class的指针起了个别名为Class。那objc_class又是什么呢?

    运行时中类的定义

    #import <objc/runtime.h>中类的定义为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;
    /* Use `Class` instead of `struct objc_class *` */
    

    在该结构体中又有一个Class类型的指针。

    所以对象的isa指针指向类对象。类对象中Class类型的isa指针指向了和类本身一样类型的类对象。即指向了元类。而元类中的isa指针指向根元类,而根元类中的isa指针指向他自己。

    关系图

    在类中除了有isa指针之外,还有

    • Class _Nullable super_class //指向父类的指针。
    • const char * _Nonnull name //类名
    • long version //版本
    • long info //信息
    • long instance_size //对象在内存中的大小
    • struct objc_ivar_list * _Nullable ivars //类中成员变量列表
    • struct objc_method_list * _Nullable * _Nullable methodLists //类中方法列表
    • struct objc_cache * _Nonnull cache //缓存
    • struct objc_protocol_list * _Nullable protocols //类所遵循的协议列表

    运行时中方法的定义

    //定义了objc_method类型的结构体指针Method
    typedef struct objc_method *Method;
    
    struct objc_method {
        SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
        char * _Nullable method_types                            OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
    }
    

    可见Method由方法名、方法类型、方法实现这三个部分组成。
    而只要知道了方法的名称,和方法所属的类就可以获得该方法的指针。
    方法的类型编码规则可参考官方文档
    通过以下方法分别获取实例方法和类方法。

    Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
    
    Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
    
    

    如何获取类对象

    在OC中不支持直接获取isa指针。

    获取isa指针

    实际上有三种方法可以获取:

    //1.OC方法
    Class a = [self class];
    
    //2. object_getClass(id _Nullable obj, Class _Nonnull cls)
    //传入实例对象
    Class b = object_getClass(self);
    
    //3.objc_getClass(const char * _Nonnull name)
    //将OC字符串转为C字符串
    const char *name = [[[self class] description] UTF8String];
    id c = objc_getClass(name);
    
    NSLog(@"类对象地址:%p  %p  %p",a,b,c);
    

    这三种方法得到的是相同的类对象。

    如何获取元类对象

    //1.object_setClass(id _Nullable obj, Class _Nonnull cls)
    Person *p = [[Person alloc] init];
    Class a = object_getClass([p class]);
    
    //2.object_setClass(id _Nullable obj, Class _Nonnull cls)
    Class b = object_getClass([Person class]); 
    
    //3.objc_getMetaClass(const char * _Nonnull name)
    Class c = objc_getMetaClass("Person");
    
    NSLog(@"元类对象地址:%p  %p %p",a,b,c);
    

    方法一和方法二实际上是同一种方式。

    object_getClass和objc_getClass有什么区别

    - (void)demo1
    {
        id currentClass = self;
        const char *a = object_getClassName(currentClass);
        for (int i = 1; i < 7; i++) {
            NSLog(@"Following the isa pointer %d times gives 1 %p---%s", i, currentClass,a);
            currentClass = object_getClass(currentClass);
            a = object_getClassName(currentClass);
        }
    }
    
    - (void)demo2
    {
        id currentClass = [[Person alloc] init];
        const char *a = object_getClassName(currentClass);
        for (int i = 1; i < 7; i++) {
            NSLog(@"Following the isa pointer %d times gives 2 %p---%s", i, currentClass,a);
            currentClass = object_getClass(currentClass);
            a = object_getClassName(currentClass);
        }
    }
    
    - (void)demo3
    {
        Class currentClass = [self class];
        const char *a = object_getClassName(currentClass);
        for (int i = 1; i < 5; i++) {
            NSLog(@"Following the isa pointer %d times gives 3 %p---%s", i, currentClass,a);
            currentClass = objc_getClass([NSStringFromClass(currentClass) UTF8String]);
            a = object_getClassName(currentClass);
        }
    }
    
    Following the isa pointer 1 times gives 1 0x7fcf61e07060---ViewController
    Following the isa pointer 2 times gives 1 0x10a0be1f0---ViewController
    Following the isa pointer 3 times gives 1 0x10a0be218---NSObject
    Following the isa pointer 4 times gives 1 0x10b068e58---NSObject
    Following the isa pointer 5 times gives 1 0x10b068e58---NSObject
    Following the isa pointer 6 times gives 1 0x10b068e58---NSObject
    
    Following the isa pointer 1 times gives 2 0x60000020dff0---Person
    Following the isa pointer 2 times gives 2 0x10a0be268---Person
    Following the isa pointer 3 times gives 2 0x10a0be240---NSObject
    Following the isa pointer 4 times gives 2 0x10b068e58---NSObject
    Following the isa pointer 5 times gives 2 0x10b068e58---NSObject
    Following the isa pointer 6 times gives 2 0x10b068e58---NSObject
    
    Following the isa pointer 1 times gives 3 0x10a0be1f0---ViewController
    Following the isa pointer 2 times gives 3 0x10a0be1f0---ViewController
    Following the isa pointer 3 times gives 3 0x10a0be1f0---ViewController
    Following the isa pointer 4 times gives 3 0x10a0be1f0---ViewController
    

    对比一下不难发现:

    在demo1中传入self对象,
    第一次打印了实例对象0x7fcf61e07060
    第二次打印了类对象0x10a0be1f0
    第三次打印了元类对象0x10a0be218,
    第四次及以后打印了根元类对象0x10b068e58,因为根元类的isa指针指向它自己。

    在demo2中同理,可见第四次及以后打印的根元类对象和demo1中的地址一样,由此可证所有的对象指向同一个根元类对象。

    在demo3中,四次打印的都是0x10a0be1f0,和demo1中的类对象地址相同。

    得出以下结论:

    • object_getClass返回的是传入对象的isa指针所指向的类。
    • objc_getClass只能返回类对象。
    • objc_getMetaClass只能返回元类对象。

    再奉上源码,与我们推测的是一样的。

    /***********************************************************************
    * object_getClass.
    * Locking: None. If you add locking, tell gdb (rdar://7516456).
    **********************************************************************/
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    /***********************************************************************
    * objc_getClass.  Return the id of the named class.  If the class does
    * not exist, call _objc_classLoader and then objc_classHandler, either of 
    * which may create a new class.
    * Warning: doesn't work if aClassName is the name of a posed-for class's isa!
    **********************************************************************/
    Class objc_getClass(const char *aClassName)
    {
        if (!aClassName) return Nil;
    
        // NO unconnected, YES class handler
        return look_up_class(aClassName, NO, YES);
    }
    
    /***********************************************************************
    * objc_getMetaClass.  Return the id of the meta class the named class.
    * Warning: doesn't work if aClassName is the name of a posed-for class's isa!
    **********************************************************************/
    Class objc_getMetaClass(const char *aClassName)
    {
        Class cls;
    
        if (!aClassName) return Nil;
    
        cls = objc_getClass (aClassName);
        if (!cls)
        {
            _objc_inform ("class `%s' not linked into application", aClassName);
            return Nil;
        }
    
        return cls->ISA();
    }
    

    通过运行时如何HOOK

    + (void)load
    {
        SEL sel_classMethod = @selector(classMethod);
        SEL sel_hook_classMethod = @selector(hook_classMethod);
        
        Class metaClass_obj = object_getClass(self);//元类对象
    //    Class class_obj = objc_getClass([[[self class] description] UTF8String]);//类对象
        
        Method m_classMethod = class_getClassMethod(metaClass_obj, sel_classMethod);
        Method m_hook_classMethod = class_getClassMethod(metaClass_obj, sel_hook_classMethod);
        
        BOOL isAdd = class_addMethod(metaClass_obj,
                                     sel_classMethod,
                                     method_getImplementation(m_hook_classMethod),
                                     method_getTypeEncoding(m_hook_classMethod));
        if (isAdd) {
            class_replaceMethod(metaClass_obj,
                                sel_hook_classMethod,
                                method_getImplementation(m_classMethod),
                                method_getTypeEncoding(m_classMethod));
        }else{
            method_exchangeImplementations(m_classMethod, m_hook_classMethod);
        }
    }
    

    值得注意的点:

    • class_addMethod的参数。有四个参数,后三个参数拼起来正好是Method的结构定义。如果方法已存在,会返回false
    • 在类方法中self代表类对象,在实例方法中self代表实例对象。object_getClass(self)同样的写法在类方法中和在实例方法中得到的结果不一样。
    • hook类方法需使用class_getClassMethod,参数传入元类对象。
    • hook实例方法需使用class_getInstanceMethod,参数传入类对象。

    总结:

    对象方法列表是保存在类中的,如果想给类添加一个对象方法,是将该方法添加到类的方法列表中。
    类方法列表是保存在元类中的,如果想要给类添加一个类方法,是将该方法添加到元类的方法列表中。

    相关文章

      网友评论

        本文标题:通过OC运行时解析对象、类、元类之间的关系

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