美文网首页
runtime(一)-Objective-C对象结构

runtime(一)-Objective-C对象结构

作者: George_Luofz | 来源:发表于2018-04-01 19:42 被阅读6次
    1. 关键是理解isa指针指向,参考经典的图

    假设有个Person类继承自NSObject:

    @interterface Person : NSObject
    - (void)eat();
    @end
    ...
    Person *person = [[Person alloc] init];
    [person eat];
    

    解释如下:

    person 对象的isa指向Person类(类也是对象)
    Person 类的isa指向Person的元类
    Person 元类isa指向NSObject的元类(根元类)

    1. 理解这几个对象分别存了啥
      类对象存对象方法
      元类对象存类方法
      根元类存根元类的类方法

    2.1 peson对象的isa指向Person类对象,它就这么一个参数,如下

    typedef struct objc_object {
        Class isa;
    } *id;
    

    2.2 Person类对象比较丰富,如下:

    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;
    
    1. isa 指向元类对象
    2. super_class 指向基类对象
    3. name 表示类名 version 版本号 instance_size:大小 我记得是48Bytes
    4. ivars 表示变量列表
    5. methodLists 方法列表
    6. cache 已调用方法的缓存
    7. protocols 协议列表

    我们调用定义的eat方法时,runtime会将该方法换成objc_msgSend(id self, SEL s, param1, param2...)方法发消息,isa作用体现在查找eat方法过程:
    person对象的isa->拿到Person类对象,找cache中是否缓存的有,有则立即调用,没有就从methodLists中找该方法,有则调用,没有则通过super_class重复该过程-> 若eat是类方法,则通过Person类对象的isa拿到元类,在元类中重复上步过程(元类的结构跟类对象的结构定义相似)-> 若没有则通过元类的isa拿到根元类,在根元类中重复上述过程->根元类的isa指向自己,向上追溯过程就结束了

    1. 理解元类和根元类
      参考:Objective-C 中的元类(meta class)是什么?
      元类是类对象的类,里边存类方法
      根元类是元类的类,所有的元类指向同一个根元类

    元类打印的类型跟类对象的类型一致,但内存地址是不一样的,元类可以通过object_getClass()传入类对象拿到
    贴一段代码:

    - (void)_test_metaClass2{
        Class newClass =
        objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
        class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
        objc_registerClassPair(newClass);
        Class metaClass =  [newClass class];
     
        id instanceOfNewClass =
        [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
        [instanceOfNewClass performSelector:@selector(report)];
        
    }
    
    void ReportFunction(id self, SEL _cmd)
    {
        NSLog(@"This object is %p.", self);
        NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
        
        Class currentClass = [self class];
        for (int i = 1; i < 4; i++)
        {
            NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
            currentClass = object_getClass(currentClass);
        }
        
        NSLog(@"NSObject's class is %p", [NSObject class]);
        NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
    }
    
    

    输出结果如下:

    2018-04-01 19:40:14.265682+0800 iOSLearnigDemo[7206:426669] This object is 0x604000453f20.
    2018-04-01 19:40:14.265950+0800 iOSLearnigDemo[7206:426669] Class is RuntimeErrorSubclass, and super is NSError.
    2018-04-01 19:40:14.266073+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 1 times gives 0x604000453710 // 类对象
    2018-04-01 19:40:14.266179+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 2 times gives 0x604000453d70 // 元类对象
    2018-04-01 19:40:14.266321+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 3 times gives 0x104b0de58 //本次打印的就是根元类
    2018-04-01 19:40:14.266452+0800 iOSLearnigDemo[7206:426669] NSObject's class is 0x104b0dea8
    2018-04-01 19:40:14.266651+0800 iOSLearnigDemo[7206:426669] NSObject's meta class is 0x104b0de58 //根元类

    2. 理解类对象结构

    参考:Objective-C Runtime
    所有定义在runtime.h中可以看到

    • ivars
    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;
    
    struct objc_ivar_list {
        int ivar_count                                           OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE; //是一个变长结构体,存放所有的变量
    }  
    
    • methodLists 结构,命名都很清晰
    struct objc_method {
        SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
        char * _Nullable method_types                            OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE; //方法实现,是一个函数指针,指向真正的方法地址
    }                                                            OBJC2_UNAVAILABLE;
    
    struct objc_method_list {
        struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;
    
        int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
    }   
    
    • protocolLists
    struct objc_protocol_list {
        struct objc_protocol_list * _Nullable next; //下个协议列表,这结构看起来像链表
        long count;
        __unsafe_unretained Protocol * _Nullable list[1]; //Protocol的定义如下文
    };
    
    #ifdef __OBJC__
    @class Protocol; //在objc下,protocol其实也是个类
    #else
    typedef struct objc_object Protocol;
    #endif
    
    • cache
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE; //此处的method就是objc_method,因为有如下定义
    };
    typedef struct objc_method *Method;
    
    3.上边一些结构的理解
    1. ivars 存所有的实例变量,包括property+exetensions中定义的实例变量,可以用代码验证;ivars 中获取的property名字是带_下划线的(exstensions中定义的直接打印原名)
    @interface Person2 : NSObject
    @property (nonatomic, strong) NSArray *array;
    @end
    @interface Person2(){
        int _age;
    }
    @end
    @implementation Person2
    
    @end
    ...
    
    - (void)_test_ivarList{
        int outCount = 0;
        Ivar *varList = class_copyIvarList(objc_getClass("Person2"), &outCount); //获取所有实例变量
        int outCount2 = 0;
        objc_property_t *propList = class_copyPropertyList(objc_getClass("Person2"), &outCount2); //获取所有属性
        
        for(int i = 0 ;i < outCount;i++){
            Ivar ivar = varList[i];
            char *name = ivar_getName(ivar);
            NSLog(@"ivar: %s\n",name);
        }
        for(int i = 0 ;i < outCount2;i++){
            objc_property_t pro = propList[i];
            char *name = property_getName(pro);
            NSLog(@"prop:%s\n",name);
        }
        free(varList);
        free(propList);
    }
    输出结果如下:
    2018-04-04 14:59:11.340351+0800 iOSLearnigDemo[7044:716357] ivar: _age
    2018-04-04 14:59:11.341156+0800 iOSLearnigDemo[7044:716357] ivar: _array
    2018-04-04 14:59:11.341385+0800 iOSLearnigDemo[7044:716357] prop:array
    

    关于ivar或者property的所有操作方法,基本都以class_开头,这也很好理解,他们属于class的范畴

    1. ivar 理解在类创建之后就无法再修改
      简单得说,ivar的内存布局在编译时已经确定,无法直接修改;在运行时调用class_setIvarLayout 和 class_setWeakIvarLayout 重新设置 Ivar Layout无效;可以有其他trick的方式
      具体参考:ObjC如何通过runtime修改Ivar的内存管理方式
    2. ivar 可以在runtime新建类时添加或修改
        Class TestObjectClass = objc_allocateClassPair(NSClassFromString(@"NSObject"), "TestObject", sizeof(48));
        
        BOOL flag = class_addIvar(TestObjectClass, "prop", sizeof(int), 0, @encode(int));
        if(flag){
            NSLog(@"%@ add ivar:prop success",TestObjectClass);
        }else{
            NSLog(@"%@ add ivar:prop success",TestObjectClass);
        }
        objc_registerClassPair(TestObjectClass);
        //打印一下
        [self _test_printf_classIvarList:TestObjectClass];
    输出结果:
    2018-04-04 15:35:41.217178+0800 iOSLearnigDemo[7590:741638] TestObject add ivar:prop success
    2018-04-04 15:35:41.217615+0800 iOSLearnigDemo[7590:741638] TestObject ivar: prop
    
    1. methodLists中,每一个method包装一个SEL和一个IMP,SEL是方法选择器(也叫选择子),IMP是一个函数指针,指向方法的具体实现;熟悉了这个结构,可以比较熟练的玩method-swizzing了,主要用method这个结构体来操作;
       // 1.拿到selector对应的method
        Method swizzMethod = class_getInstanceMethod([self class], @selector(_test_person_replace_method));
        Method originalMethod = class_getInstanceMethod([Person2 class], @selector(test));
        // 2.尝试为要替换的类添加一个method
        BOOL success = class_addMethod([Person2 class], @selector(test), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        // 2.1 添加成功;说明替换类中没有test方法,则说明父类有实现,将替换方法实现改为父类的实现
        if(success){
            class_replaceMethod([Person2 class], @selector(_test_person_replace_method), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else{
            // 2.2 未添加成功,直接交换method的imp
            method_exchangeImplementations(originalMethod, swizzMethod); //直接替换两个实现
        }
        
        [[Person2 new] test];
    
    1. 方法替换主要用method结构体来操作
    2. SEL转method,SEL转imp都可以通过class来操作
    4. 补充一些结构

    4.1 SEL

    • 方法选择器,是objc_msgSend()的第二个参数,可以理解成区分方法的id,这个id得结构如下:
    参考:objc/objc.h头文件中声明
    /// An opaque type that represents a method selector.
    typedef struct objc_selector *SEL; //objc_selector未看到具体声明
    
    • 有三种方式获取SEL
        SEL selector1 = @selector(_test_runtime_method);
        SEL selector2 = sel_registerName("_test_runtime_method");
        SEL selector3 = NSSelectorFromString(@"_test_runtime_method");
        NSLog(@"1:%p,2:%p,3:%p",selector1,selector2,selector3);
    日志输出:
    2018-04-04 17:07:16.538913+0800 iOSLearnigDemo[8950:800180] 1:0x10f436a24,2:0x10f436a24,3:0x10f436a24 //获取的都是一样的
    
    • 不同类中的相同名字的方法对应的SEL是一样的[经过我测试,打印两个不同类相同名称的Selector,得到的结果是不一样的,暂时有个疑惑],即使方法的参数类型不一样也无效;(有参数和无参数肯定是不同的)

    • 实际上函数执行前,就是先根据SEL找IMP,具体我们在objc_msgSend()方法学习时再展开

    • 在当前函数中打印_cmd获取到的也是它,每一个方法都有两个隐藏参数self和_cmd

    4.2 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
    

    相关文章

      网友评论

          本文标题:runtime(一)-Objective-C对象结构

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