iOS中类和对象的底层原理

详细讲解iOS类和对象的本质,一层层一步步解开类和对象的神秘面纱,如果有不对的地方还请指导。
文章最后有个测试用例可以用来检验类和对象的底层实现原理

先定义Person类

下面这个是用一个Person类来讲解底层实现原理,

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import <objc/message.h>


@interface Person:NSObject
{
@public
    //定义一个私有属性,这个属性系统不会生成set/get方法
    int _age;
}
//定义一个property类型的属性,这个属性系统会帮我们生成set/get方法
@property(nonatomic,assign)int height;
-(void)run;
+(void)home;
@end

@implementation Person
-(void)run{
    NSLog(@"=======调用了 run 方法");
}

+(void)home{
}
@end

开始讲解->把Person.m文件转成.cpp

#pragma mark ########### 以下是把.m文件转成.cpp后的样子,在这里我把关键的部分取出来,写在这里;
#pragma mark###########  Person start 
#pragma mark########## 以下是把.m文件转成.cpp后的样子,在这里我把关键的部分取出来,写在这里;
#pragma mark##########  Person start 
/*
 Object-C中所有继承NSObject的类 底层就是一个结构体,所以我们把OC类转成.cpp文件后,看到的就是一个结构体
 */

/*
 这个结构体是NSObject类底层的样子,说明NSObject类只有一个isa属性,isa是一个指针类型占8个字节,说明NSObject的一个对象实际内存是8字节;
 但是系统分配内存的时候却分配了16个字节,在下面这个函数中我们可以看到原因,当内存字节小于16字节时返回16字节的长度;
 size_t instanceSize(size_t extraBytes) {
     size_t size = alignedInstanceSize() + extraBytes;
     // CF requires all objects be at least 16 bytes.
     if (size < 16) size = 16;
     return size;
 }
 综上所述:一个NSObject对象的占内存长度是16个字节
 */
struct NSObject_IMPL {
    Class isa;
};

/*
 这个就是Person类的底层结构体;我们可以看到结构体中有两个属性:
 1)NSObject_IVARS属性:是NSObject_IMPL类型的属性,也可以理解为isa指针;因为NSObject_IMPL只有一个isa属性
 2)_age 就是我们自定义的属性
 */
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
};
/*
 实例方法
 */
static void _I_Person_run(Person * self, SEL _cmd) {
}
/*
 类方法
 */
static void _C_Person_home(Class self, SEL _cmd) {
}
/*
 我们发现无论是对象方法还是类方法都没有在结构体内部,c++的结构体是可以定义方法的,但是为什么不在结构体内部定义;
 这里是把方法实现单独定义成了函数,这些函数会单独存放在一个内存区域,这个函数的内存区域和结构体对象的内存区域是分开的;
 也就是说结构只存储属性值,比如我们初始化一个结构体,系统就会给这个结构体分配内存,内存占用的多少是根据结构体的属性的多少来分配的;
 如果结构体内部有方法的话,也会给方法分配内存,这样就会消耗大量内存;
 如果把结构体的方法单独存放在一个内存区域,当我们取调用结构体对象的方法的时候,去这个内存区域找到对应的函数方法,并把结构体对象也传给这个方法,结构体对象相当于上下文功能,
 这样就不用给每个结构体实例的方法都分配内存,结构体所有对象共用一个方法内存,结构体对象只需要保存好属性的值的内存就可以了;
 下面会讲方法存储在什么地方
 */

/*
 这个就是@property(nonatomic,assign)int height;由系统生成的set/get方法
 */
static int _I_Person_height(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)); }
static void _I_Person_setHeight_(Person * self, SEL _cmd, int height) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)) = height; }

#pragma mark############ Person end ##############
#pragma mark#############到这就是Person类被转成底层时的样子##
#pragma mark############  Person end  

Person的属性讲解

我在main文件中定义了Person类,

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import <objc/message.h>


@interface Person:NSObject
{
@public
    //定义一个私有属性,这个属性系统不会生成set/get方法
    int _age;
}
//定义一个property类型的属性,这个属性系统会帮我们生成set/get方法
@property(nonatomic,assign)int height;
-(void)run;
+(void)home;
@end

@implementation Person
-(void)run{
    NSLog(@"=======调用了 run 方法");
}

+(void)home{
}
@end

int main(int argc, const char * argv[]) {
    
    //问了验证runtime,需要把ARC关闭,换成MRC
    
    Person *p = [[Person alloc] init];
    p->_age = 20;
    int a = p->_age;
    p.height = 100;
    int h = p.height;
    [p run];
    [Person home];
    
    return 0;
}

把main文件转成cpp文件,部分代码删掉了


/*
 下面就是对Person的属性的配置
 */

/*
 这个定义是用来记录Person_IMPL结构体中的_age属性的内存偏移量;
 因为当我们给Person对象的_age属性设置值或者获取_age的值,都需要知道_age属性在内存中的位置,
 然后找到_age内存,把值写入到_age内存中,或者从_age内存中读取值
 */
extern "C" unsigned long OBJC_IVAR_$_Person$_age;
extern "C" unsigned long OBJC_IVAR_$_Person$_height;

/*
 用来计算OBJC_IVAR_$_Person$_age的值
 */
extern "C" unsigned long int OBJC_IVAR_$_Person$_age __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person_IMPL, _age);

extern "C" unsigned long int OBJC_IVAR_$_Person$_height __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person_IMPL, _height);


int main(int argc, const char * argv[]) {
    
    /*
     这里是初始化一个Person对象;
     Person *p = [[Person alloc] init];
     */
    Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    
    /*
     p->_age = 20;
     
     这里是给Person对象的_age属性赋值,我们来分解这个式子到底是什么意思;
     0)其中p是Person类的一个对象,实际就是一个指针;是指针就好办了,我们可以转换成其他任意类型的指针;注意在ARC中编译器不让转化,可以在MRC中转换,把xcode修改成MRC;
     1)(char *)p 的意思是把p转换成char *类型的指针;
     2)((char *)p + OBJC_IVAR_$_Person$_age) 这个就是指针的移动,由于先前已经转换长char*类型的,所以指针移动的步长是一个字节,
        总共移动OBJC_IVAR_$_Person$_age长度的字节,OBJC_IVAR_$_Person$_age就是_age属性在Person_IMPL结构体中的内存偏移量;
        OBJC_IVAR_$_Person$_age的长度是这样计算的:__OFFSETOFIVAR__(struct Person_IMPL, _age)
     3)(int *)((char *)p + OBJC_IVAR_$_Person$_age)这一步是指针移动完成后,把指针转成int *类型的;这是因为_age属性是int类型的;
        转成int *类型是为了方便赋值或者取值;
     4)*(int *)((char *)p + OBJC_IVAR_$_Person$_age)这一步我们看到在3)的基础上最左边添加了一个*号,由于式子是在等号=左边,
        所以加*号是赋值的意思,
     */
    (*(int *)((char *)p + OBJC_IVAR_$_Person$_age)) = 20;

    /*
     int a = p->_age;
     
     这里是取值,取Person对象的_age属性值;这里就不做过多介绍了
     */
    int a = (*(int *)((char *)p + OBJC_IVAR_$_Person$_age));
    
    /*
     下面是调用@proper描述的属性,我们可以发现和调用_age属性不同;
     这里调用了set/get方法;而不是直接给属性赋值或者直接取属性的值;
     其实是在set/get方法内部实现了取值和赋值操作;
     不同点就是一个直接操作内存,另一个通过消息发送的方式调用set/get方法取操作内存;
     效率的话,肯定是直接操作内存快;
     
     */

    /*
     p.height = 100;
     
     这是height的set方法,其方法内部实现和_age属性一样
     _I_Person_setHeight_(Person * self, SEL _cmd, int height) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)) = height; }
     */
    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)p, sel_registerName("setHeight:"), 100);

    /*
     int h = p.height;
     
     这是height属性的get方法,我们可以看到方法内部的实现和_age属性一样
     _I_Person_height(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)); }
     */
    int h = ((int (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("height"));

    
    /*
     调用对象方法
     
     [p run];
     */
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));

    /*
     调用类方法
     
     [Person home];
     */
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("home"));
    /*
     调用对象方法和类方法不同点在于:
     调用对象方法是直接传p对象;
     调用类方法是获取类对象objc_getClass("Person")
     */
    
    return 0;
}

Person的属性和方法是怎么保存的

#pragma mark ########### 以下是 Person的属性和方法是怎么保存的 #####
#pragma mark #######################################################

/*
 从转换后的.cpp文件中可以看到;Person类的变量,属性,方法,isa,父类的isa,以及缓存都可以从这里找到;
 _class_ro_t结构体非常重要,里面保存的都是Person类的所有的数据结构;
 
 也就是说我们这样Person *p = [[Person alloc] init];创建的p对象;p的isa指针指向的就是_class_t结构体对象;
 也即是Person *p = [[Person alloc] init];
 可以把p->isa转成:
 struct _class_t *c = (struct _class_t *)(p->isa);
 
 对于Person类来说,全局只有一个Person类对象,记住是类对象,不是Person对象,类对象全局只有一个,保存的是这个类的所有数据结构;
 这个类对象就是_class_t对象,我们创建p对象的时候,会把类对象(也就是_class_t对象)的指针地址保存一份到p对象的isa上;
 也就是我们每创建一个p对象,p对象都会在isa上保存一份类对象的指针地址
 
 这样有什么用呢?非常重要,比如:
 我们想要获取_age的值,平常我们是直接p._age就可以获取到,但是系统在底层帮我们做了好多事;
 1)首先我们要知道Person类有没有这个属性?
 2)我们还要知道_age属性在内存中的位置吧,不然怎么获取值呢?
 3)找到这个属性后才能对这个属性操作;
 所以Person的所有数据结构都保存在_class_t这个结构体中;
 下面一步步讲解
 */
struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

/*
 这个结构体体保存的都是Person类的具体内容了;一个一个看:
 flags:不清楚,不用关心
 instanceStart:这个就是Person对象在内存地址中开始的位置,如果我们要获取某个对象,就需要先拿到这个属性,通过指针移动,获取到这个对象内存的起始位置
 instanceSize :这个就是对象的内存长度,也就是这个对象在内存中占用的字节数
 reserved:不清楚
 ivarLayout:不清楚
 name:类名
 baseMethods:保存方法的地方,类的方法都保存在这里
 baseProtocols:保存协议的地方
 ivars:保存对象变量的地方,就是在{int _age;}定义的变量;
 weakIvarLayout:不清楚
 properties:保存属性的地方,就是@proper()int height;定义的属性;
 */
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

/*
 先看变量列表;在.cpp文件中可以找到这个;这个就是保存变量的列表
 entsize:是_ivar_t结构体的内存长度
 count:保存变量的数量,
 ivar_list:保存真的变量的具体内容,看下面的_ivar_t介绍
 */
static struct _ivar_list_t {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[2];
}
/*
 这个是描述变量的,也就是这个变量的名字,类型,占用的内存字节数,内存偏移量
 offset:保存这个变量在结构体内存中的内存偏移量;方便找到这个变量的位置;类似_class_ro_t结构体中的instanceStart属性
 name:变量名字
 type:变量的类型,比如int,float,String等,是简写的类型;
 alignment:字节对齐
 size:这个变量占的内存长度;类似_class_ro_t结构体中的instanceSize属性
 */
struct _ivar_t {
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;
    const char *type;
    unsigned int alignment;
    unsigned int  size;
};

/*
 这个是方法列表;类的所有方法都保存在这个地方;
 entsize:保存的是_objc_method结构体的占内存的大小;
 method_count:保存的是方法数量
 method_list:所有的方法都保存在这个数组中;
 一个方法的具体内容保存在_objc_method结构体中;
 继续往下看
 */
static struct _method_list_t {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
}
/*
 这个是描述一个方法;也就是这个方法具有哪些内容;
 _cmd:方法的名字
 method_type:方法的类型,像这样"v16@0:8",描述这个方法的返回值类型,参数类型等
 _imp:函数指针,可以理解为方法的实现的地址,通过这个指针就能拿到这个方法;从而调用这个方法;
      runtime里面的黑魔法,方法交换;其实就是修改_imp指针,让它指针另一个函数的地址;
 */
struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};

/*
 _objc_protocol_list  协议列表
 _prop_list_t  属性列表
 有时间在讲解
 */

类对象 到底是怎么产生的

#pragma mark #########################  类对象 到底是怎么产生的? ##############################
#pragma mark #########################  类对象 属性和方法是怎么存储的?  ##############################

/*
 这里是类对象实现的地方
 
 在.cpp文件我们看到,这里初始化了一个_class_t类型的变量 OBJC_CLASS_$_Person
 OBJC_CLASS_$_Person就是 Person 类对象,全局只有这一个;
 我们发现初始化的时候:
 struct _class_t *isa; 这个是0,也就是说类对象初始化的时候,他的isa指针式空的,别担心,后面会对他从新赋值
 struct _class_t *superclass; 这个也是0,也就是说类对象初始化的时候,他的指向父类的superclass指针式空的,别担心,后面会对他从新赋值
 void *cache;  缓存也是空的
 void *vtable; 也是空的
 struct _class_ro_t *ro; 这个有值_OBJC_CLASS_RO_$_Person,这个值保存的就是Person类的数据结构,保存的属性,方法,协议等
 我们全局找到_OBJC_CLASS_RO_$_Person这个在那实现的;请继续看下面
 */
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_Person,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_Person,
};

/*
 我们发现_OBJC_CLASS_RO_$_Person是_class_ro_t类型的结构体对象;
 可以看到
 unsigned int flags; 值是0
 unsigned int instanceStart;可以看到内存初始位置就是竟然是Person类的第一个自定义属性_age的指针位置;竟然不是isa的指针位置,那是因为isa保存的是类对象的位置,
 如果从isa开始取值,就取不到_age,和其他属性的值了;
 unsigned int instanceSize;可以看到内存长度是Person_IMPL结构体的长度
 unsigned int reserved; 值是0
 const unsigned char *ivarLayout;也是0
 const char *name; 类名"Person"
 const struct _method_list_t *baseMethods; 这个有值,_OBJC_$_INSTANCE_METHODS_Person,这个保存的是方法列表
 const struct _objc_protocol_list *baseProtocols;也是0,因为我们没有定义协议,所以没有
 const struct _ivar_list_t *ivars;保存的是变量列表
 const unsigned char *weakIvarLayout;应该是存的弱引用
 const struct _prop_list_t *properties;属性列表
 
 下面主要看方法列表_method_list_t和_ivar_list_t变量列表
 
 */
static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0,
    __OFFSETOFIVAR__(struct Person, _age),
    sizeof(struct Person_IMPL),
    (unsigned int)0,
    0,
    "Person",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
    0,
    (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
};

/*
 这里是方法列表。初始化方法列表
 我们可以看到run,这个是我们自己定义的方法
 还有height和setHeight,这个是@property()定义的属性,系统帮我们实现了set/get方法
 下面我们看看变量列表是怎么实现的
 
 _I_Person_run
 _I_Person_height
 _I_Person_setHeight_
 这三个就是函数指针,保存的是函数实现的位置
 当我的Person对象调用run方法的时候,其实就是根据“run”字符串去找到_I_Person_run这个函数指针;调用这个函数;
 我们发现_I_Person_run函数的第一个参数就是Person类型的,
 */
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    5,
    {{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_run},
    {(struct objc_selector *)"height", "i16@0:8", (void *)_I_Person_height},
    {(struct objc_selector *)"setHeight:", "v20@0:8i16", (void *)_I_Person_setHeight_},
    }
};


/*
 这个就是变量列表,初始化变量列表
 在.cpp文件中我们可以找到这个,这个就是把Person类的所有属性保存在 _OBJC_$_INSTANCE_VARIABLES_Person 这个变量里;
 _INSTANCE_VARIABLES_Person 是_ivar_list_t类型的,上面有介绍;
 这个式子的意思是定义一个_ivar_list_t类型的结构体,并且创建一个结构体对象_OBJC_$_INSTANCE_VARIABLES_Person 并给这个对象初始化;初始化的时候把Persion类的属性都放进去了;
 
 */
static struct _ivar_list_t {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    2,
    {
        /*在这里说一下:
         OBJC_IVAR_$_Person$_age :这个在上面有定义过,就是_age属性在结构体内存中的偏移量;
         "_age":是属性的名字
         "i":是属性的类型;
         2:未知,可能是属性的数量;
         4:就是这个属性的内存长度因为是int类型,所以长度是4
         */
        {(unsigned long int *)&OBJC_IVAR_$_Person$_age, "_age", "i", 2, 4},
        {(unsigned long int *)&OBJC_IVAR_$_Person$_height, "_height", "i", 2, 4}
    }
};

重要, isa指针什么时候赋值的

#pragma mark #############  重要  isa指针什么时候赋值的?  ############
/*
 重要
 其实在这里赋值的,
 首先是NSObject的元类对象给Person的元类isa赋值;
 最后Person的元类对象赋值给Person类对象的isa
 */
static void OBJC_CLASS_SETUP_$_Person(void ) {
    OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
    OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
    OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
}

#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
    (void *)&OBJC_CLASS_SETUP_$_Person,
};

元类对象 到底是怎么产生的

#pragma mark #########################   元类对象 到底是怎么产生的?  ##############################


/*
 这里是元类对象实现的地方
 
 在.cpp文件我们看到,这里初始化了一个_class_t类型的变量 OBJC_METACLASS_$_Person
 OBJC_METACLASS_$_Person就是 Person 元类对象,全局只有这一个;
 我们发现初始化的时候:
 struct _class_t *isa; 这个是0,也就是说元类对象初始化的时候,他的isa指针式空的,别担心,后面会对他从新赋值
 struct _class_t *superclass; 这个也是0,也就是说元类对象初始化的时候,他的指向父类的superclass指针式空的,别担心,后面会对他从新赋值
 void *cache;  缓存也是空的
 void *vtable; 也是空的
 struct _class_ro_t *ro; 这个有值,这个值保存的就是Person元类的数据结构, 基本上保存的是类方法,+加号方法
 我们全局找到_OBJC_METACLASS_RO_$_Person这个在那实现的;请继续看下面
 */
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_Person,
};

/*
 我们发现上面的_OBJC_METACLASS_RO_$_Person是在这里实现的;
 可以看到
 unsigned int flags; 值是1
 unsigned int instanceStart;可以看到内存初始位置就是_class_t结构体的长度
 unsigned int instanceSize;可以看到内存长度
 unsigned int reserved; 值是0
 const unsigned char *ivarLayout;也是0
 const char *name; 类名"Person"
 const struct _method_list_t *baseMethods; 这个有值,_OBJC_$_CLASS_METHODS_Person,我们可以看到初始化的时候保存的是类方法,也就是+加号方法
 const struct _objc_protocol_list *baseProtocols;也是0
 const struct _ivar_list_t *ivars;也是0
 const unsigned char *weakIvarLayout;也是0
 const struct _prop_list_t *properties;也是0
 
 我们取看看_method_list_t的值是在哪初始化的,这个一开始保存的是类方法,也就是+加号方法
 去找找类方法在哪实现的
 
 */
static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    sizeof(struct _class_t),
    sizeof(struct _class_t),
    (unsigned int)0,
    0,
    "Person",
    (const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Person,
    0,
    0,
    0,
    0,
};

/*
 类方法在这里实现的;我们发现类方法实现的方式和对象方法实现的是一样的,
 那就不多介绍了
 */
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"home", "v16@0:8", (void *)_C_Person_home}}
};

下面代码可以测试iOS类和对象的底层原理

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import <objc/message.h>
#import "ClassInfo.h"  //把系统runtime的结构体在这里从新写了一遍,用来测试

@interface Person:NSObject
{
@public
    //定义一个私有属性,这个属性系统不会生成set/get方法
    int _age;
}
//定义一个property类型的属性,这个属性系统会帮我们生成set/get方法
@property(nonatomic,assign)int height;
-(void)run;
+(void)home;
@end

@implementation Person
-(void)run{
    NSLog(@"=======调用了 run 方法");
}

+(void)home{
}
@end

int main(int argc, const char * argv[]) {
    
    //问了验证runtime,需要把ARC关闭,换成MRC
    
    Person *p = [[Person alloc] init];
    /*
     Person对象有两个属性_age和height;都是int类型
     我们知道Person底层对呗转成一个结构体
     struct Person_IMPL {
         struct NSObject_IMPL NSObject_IVARS;
         int _age;
         int _height;
     };
     现在我们把p对象当成Person_IMPL结构体使用
     */
    //首先给p对象的两个属性设置值
    p->_age = 0xAABBCCDD;
    p.height = 0xEE1122FF;
    
    NSLog(@"==转换前==_age==%x",p->_age);
    NSLog(@"==转换前==height==%x",p.height);
    
    //转换p对象的指针类型
    int *Q = (int *)p;
    NSLog(@"==取值==_age=%x",*(Q+2));
    NSLog(@"==取值==height=%x",*(Q+3));
   
    //修改值
    *(Q+2) = 0x11223344;
    *(Q+3) = 0x55667788;
    NSLog(@"==修改值==_age=%x",*(Q+2));
    NSLog(@"==修改值==height=%x",*(Q+3));
    
    NSLog(@"==修改值==p->_age==%x",p->_age);
    NSLog(@"==修改值==p.height==%x",p.height);
    
    //p确实是一个指针,并且可以通过指针移动获取值
    
    /*
     我们在ClassInfo.m文件里定义了mj_objc_class结构体,
     这个结构体和系统底层的结构体相似,可以互相转换
     */
    
    /*
     首先我们把Person类对象进行转换
     */
    struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)p.class;
    
    
    /*
     我们拿到class_rw_t结构体对象,
     这个结构体保存了Person类的所有数据结构体
     */
    struct  class_rw_t *personClassData = personClass->data();
    
    NSLog(@"===类名===%@",[[NSString alloc] initWithCString:personClassData->ro->name encoding:NSUTF8StringEncoding]);
    NSLog(@"===类起始内存===%u",personClassData->ro->instanceStart);
    NSLog(@"===类内存长度===%u",personClassData->ro->instanceSize);
    
    //获取属性变量的数量
    int count = personClassData->ro->ivars->count;
    
    NSLog(@"===属性变量===");
    
    for(int i = 0;i<count;i++){
        const ivar_t * iv_t = (const ivar_t *) &personClassData->ro->ivars->first;
        ivar_t iv = *(iv_t + i);
        NSLog(@"===name===%@",[[NSString alloc] initWithCString:iv.name encoding:NSUTF8StringEncoding]);
        NSLog(@"===offsett===%d",*iv.offset);
        NSLog(@"===alignment_raw==%d",iv.alignment_raw);
        NSLog(@"===size===%d",iv.size);
    }
    
    NSLog(@"===方法===");
    
    //获取方法数量
    count = personClassData->ro->baseMethodList->count;
    
    for(int i = 0;i<count;i++){
        const method_t * iv_t = (const method_t *) &personClassData->ro->baseMethodList->first;
        method_t iv = *(iv_t + i);
        NSLog(@"===types===%@",[[NSString alloc] initWithCString:iv.types encoding:NSUTF8StringEncoding]);
        NSLog(@"===sel name===%@",NSStringFromSelector(iv.name));
        NSLog(@"===imp===%p",iv.imp);
    }
    
    /*
     直接调用objc_msgSend函数
     */
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
    /*
     把p对象转成Q指针后在调用objc_msgSend函数
     */
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)Q, sel_registerName("run"));
    
    
    return 0;
}

ClassInfo.h

我在这个文件把系统runtime的结构体在这里从新写了一遍,用来测试。

#import <Foundation/Foundation.h>

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct mj_objc_object {
    void *isa;
};

/* 类对象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值