iOS底层原理-OC对象的本质及分类

923 阅读7分钟

OC对象的本质及分类

oc是一门面向对象的语言,底层实现其实都是c\c++代码,所以oc的面向对象都是基于c\c++的数据结构实现的,oc的对象、类主要是基于c\c++中的结构体来实现的

一、OC对象的本质

Object-C的底层都是通过C/C++来实现的,所以OC中的对象也会转化成C/C++中的某一个数据结构

image.png

1、如何将oc代码转换为c\c++代码?

终端进入要转换的文件目录下,执行以下命令

OC对象本质 % clang -rewrite main.m -o main.cpp

不同平台支持的代码不一样 平台: Windows,mac,iOS; 架构: 模拟器(i386),32bit(armv7),64bit(arm64)

OC对象本质 % xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

创建实例对象obj

NSObject *obj = [[NSObject alloc] init];

将oc代码转化为c++代码,我们可以看到NSObject的底层结构是:

struct NSObject_IMPL {
    Class isa;
};

2、实例对象占据的内存空间大小

NSObject最终会转化成一个结构体,内部只有一个指向对象的结构体指针。 所以NSObject对象实际只是使用了8个字节类存储指针,但是系统实际分配了16个内存空间

// 实例对象的成员所占用的大小8 (实际使用的)
NSLog(@"%zd",class_getInstanceSize([NSObject class]));

// 整个结构体占用的是16(系统实际分配的)
NSLog(@"%zd",malloc_size(( **__bridge** **const** **void** *)(obj)));

通过阅读源码我们得知,当创建的对象分配的内存空间小于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;
    }

3、sizeof、class_getInstanceSize、malloc_size的区别

  • sizeof: 是一个运算符,获取的是类型的大小(int、size_t、结构体、指针变量等),程序编译时获取

  • class_getInstanceSize: 是一个函数,程序运行时才获取,创建的对象加所有实例变量实际占用的内存大小, 内存对齐一般是以【8】对齐

  • malloc_size: 在堆中开辟的大小,向系统申请的空间大小 在Mac、iOS中的malloc函数分配的内存大小总是【16】的倍数

4、objc_getClass和object_getClass的区别

  1. Class objc_getClass(const char *aClassName)
  • 传入字符串类名

  • 返回对应的类对象

  1. Class object_getClass(id obj)

1)传入的obj可能是instance对象,class对象,meta-class对象

2)返回值

  • 如果是instance对象,返回class对象

  • 如果是class对象,返回meta-class对象

  • 如果是meta-class对象,返回NSObject(基类)的meta-class对象

  1. -(Class)class,+(Class)class
  • 返回的就是类对象,不管调用多少次

5、实例验证

1、 一个Person类继承了NSObject并且有两个int属性,占用内存多少

struct Person_IMPL {
    Class isa; //8
    int _age; //4
    int _height; //4
};
@interface Person : NSObject
{
    @public
    int _age;
    int _height;
}
@end

Person *per = [[Person alloc] init];
per->_age = 18;
per->_height = 170;
// 18 170
NSLog(@"%d, %d", per->_age, per->_height);

// 16 16
NSLog(@"%zd %zd",class_getInstanceSize([Person class]),
malloc_size(( **__bridge** **const** **void** *)(per)));

2、Student类继承Person,Person继承NSObject,占用内存多少?

@interface Person : NSObject
{
    @public
    int _age;
    int _height;
}
@end
@interface Student : Person
{
    @public
    int _score;
}
@end

转换后的结构体结构如下:

struct NSObject_IMPL {
    Class isa;
};
struct Person_IMPL {
//    struct NSObject_IMPL NSObject_IVARS;
    Class isa;
    int _age;
    int _height;
};

struct Student_IMPL {
//    struct Person_IMPL Person_IVARS;
    Class isa;  //8
    int _age; // 4
    int _height; // 4
    int _score;// 4
};

验证:

Student *stu = [[Student alloc] init];
stu->_age = 20;
stu->_height = 180;
stu->_score = 85;

// 20 180
NSLog(@"%d, %d, %d", stu->_age, stu->_height, stu->_score);

// 24 实际只需占用20,但是根据内存对齐原则,应该是8的最小倍数 所以是 24
NSLog(@"%zd",class_getInstanceSize([Student class]));

// 32 系统分配
NSLog(@"%zd",malloc_size(( **__bridge** **const** **void** *)(stu)));

分析:Stu对象实际需要20,根据class_getInstanceSize的内存对齐原则【8】的倍数所以是24;而malloc_size实际申请空间,根据系统分配原则【16】倍数,所以结果是32

二、OC对象的分类

Object-C中的对象,简称OC对象,主要可以分为3种:

  • instance对象(实例对象)
  • class对象(类对象)
  • meta-class对象(元类对象)

1、instance对象

instance对象就是通过alloc出来的对象,每次调用alloc都会产生新的instance对象

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

object1、object2是NSObject的instance对象(实例对象)

它们是不同的两个对象,分别占据着两块不同的内存

instance对象在内存中存储的信息包括:

  • isa指针
  • 其他成员变量

image.png

2、class对象

类在内存中也是以对象形式存储的, 类型:Class类型; 创建:

//实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

//类对象
// class方法返回的一直都是class对象 不管调用多少次
Class classObj = [[object1 class] class];
Class objectClass1 = [object1 class];
Class objectClass2 = [object1 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object1);
Class objectClass5 = [NSObject class];

//两个实例对象的地址不同
NSLog(@"%p, %p",object1,object2); 

//这五个class对象的地址是一样的 是同一个对象
NSLog(@"%p,%p,%p,%p,%p,%p",classObj,objectClass1,objectClass2,objectClass3,objectClass4,objectClass5);

每个类在内存中有且只有一个类对象,地址都一样 class对象在内存中存储的信息主要包括:

  1. isa指针
  2. superclass指针
  3. 类的属性信息(@property)
  4. 类的对象方法信息(-号开头的方法 instance method )
  5. 类的协议信息(protocol)
  6. 类的成员变量信息(指的是成员变量的描述信息,如哪些变量,是什么类型,并不是实例对象的具体变量值)

image.png

类对象的本质结构

3、meta-class对象(元类对象)

Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"%p",objectMetaClass);

objectMetaClass就是NSObject的元类对象,元类对象也是每个类在内存中有且只有一个,元类对象和类对象在结构上非常相似。 元类对象在内存中存储的主要信息有:

  1. isa指针
  2. superclass指针
  3. 类方法(+号开头的方法 class method)

image.png

通过object_getclass方法既能获得元类对象 也能获得类对象

通过查看源码我们可以得知object_getclass会判断传进来的参数是类对象还是实例对象  如果是实例对象则返回类对象  如果传进来的是类对象则返回元类对象

可以通过下面的函数来判断对象是不是元类对象

BOOL result = class_isMetaClass(classObj); //no
BOOL result2 = class_isMetaClass(objectMetaClass); //yes

也就是说通过alloc创建的是实例对象  通过object_getclass(类对象)创建的是元类对象  其他对象则是类对象  但是类对象和元类对象有且只有一个 三类对象中 都含有isa指针,那么这个isa指针指向什么?

三、isa和superclass

1、isa指针

1)三类对象中的isa指向关系

三类对象中 都含有isa指针 其中isa指针的关系如下

实例对象的isa指向类对象  类对象的isa指向元类对象  元类对象的isa指向基类的元类对象 eg:一个实例对象想要调用对象方法,但是对象方法存放在类对象中,那么就是通过isa指针找到类对象中的对象方法再进行调用;同理,当调用类方法的时候,类方法是存放在元类对象中,类对象会通过isa指针找到元类对象,读取类方法列表中的类方法进行调用。

image.png 注:meta-class的isa指针指向基类的meta-class

2)对象的isa指针地址

由上知悉,实例对象的isa指针指向的是class对象,那么正常isa的指针地址其实就是class对象的地址。但是 从64bit开始,isa需要进行一次位运算(&ISA_MASK),才能计算出真实地址。

image.png

查看oc源码 opensource.apple.com/tarballs/ob… 可以看到ISA_MASK的定义

define ISA_MASK        0x0000000ffffffff8ULL

image.png

image.png

2、superclass指针

在类对象和元类对象中 都有一个superclass指针,其作用类似,都是指向父类对象

1)类对象中的superclass指针:

比如现在有一个Person对象继承自NSObject  有一个Student继承自Person  当studen的实例对象调用对象方法的时候,

  1. 首先实例对象会根据自己的isa指针去类对象中找有没有对应的方法  
  2. 没有的话类对象会根据自己的superclass指针去父类的类对象中去查找(也就是student的类对象根据superclass指针去Person的类对象中去查找有没有对应的对象方法)  
  3. 再没有的话Person的类对象会根据自己的superclass指针去NSObject的类对象中去寻找 (寻找到基类在没有对应方法的话就会报方法找不到的错误)

image.png

2)元类对象中的superclass指针

也是指引类对象去父类对象中寻找对应的类方法:

按照上面的例子,Student这个类 想调用一个类方法,

  1. 首先是Student的类对象 根据isa指针去Student的元类对象中查找有没有对应的类方法  
  2. 没有的话Student的元类对象会根据自己的superclass指针去父类的元类对象(也就是Person的元类对象)中查找有没有对应的类方法,
  3. 再没有的话Person的元类对象再根据自己的superclass指针去NSObject的元类对象中寻找 有的话进行调用 
  4. 没有的话NSObject的元类对象会根据superclass指针去NSObject的类对象中去寻找是否有相同名称的对象方法(这个地方下面会具体讲到为什么基类的superclass指针会指向对应的类对象)

image.png

3)isa、superclass总结

  • instance的isa指向class

  • class的isa指向meta-class

  • meta-class的isa指向基类的meta-class

  • class的superclass指向父类的class(如果没有父类,superclass指针为nil)

  • meta-class的superclass指向父类的meta-class(基类的meta-class的superclass指向基类的class

  • instance调用对象方法的轨迹(isa找到class,方法不存在,就通过superclass找父类)

  • class调用类方法的轨迹(isa找到meta-class,方法不存在,就通过superclass找父类)

    image.png