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

2018-04-01  本文已影响6人  George_Luofz
  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中可以看到

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; //是一个变长结构体,存放所有的变量
}  
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;
}   
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
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/objc.h头文件中声明
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL; //objc_selector未看到具体声明
    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 //获取的都是一样的

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
上一篇下一篇

猜你喜欢

热点阅读