OC底层:OC的本质

作者: lcc小莫 | 来源:发表于2018-08-07 14:36 被阅读153次

通过常见的面试题问题,来分析OC的底层技术,本文主要是学习小码哥课程关于底层的总结,只有记录下来和理解了,才能转变为自己的。

一 、查看OC的底层实现

  • 我们平时编写的Object-C代码,底层都是基于C、C++的数据结构实现的


    OC代码转化的过程

    先思考几个问题:
    1、 Object-C的对象、类主要是基于C、C++的什么数据结构实现的呢?
    2、NSObject对象在内存中占几个字节?
    2、一个 Object-C对象,在内存中是如何布局的?

下面我们来创建一个Object-C对象,来转换为C++的文件,来分析OC的底层实现。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
        
    return 0;
}

cd到main.m的目录文件,然后使用clang命令行

//未指定终端架构,生成了main.cpp文件
clang -rewrite-objc main.m -o main.cpp 

//指定arm64位架构,生成main-arm64.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

直接点NSObject,进去查看OC中的实现方式

// NSObject在底层的实现
 @interface NSObject {
     Class isa ;
 }

我们打开arm64.cpp 文件,在里面找到

// NSObject在C++中的实现
struct NSObject_IMPL {
    Class isa;
};
// Class的本质是一个指针
typedef struct objc_class *Class;

发现在C++中,OC是以一个结构体实现的,而结构体中只有一个成员变量isa,点击Class进去查看,发现原来isa是一个指针。

所以,可以得出结论:NSObject主要是基于C、C++的结构体实现的,结构体里面只有一个isa成员变量,而isa又是一个指针,一个指针在64位中占的是8个字节,所以NSObject对象在64位中内存占8个字节。

同时我们也可以用代码打印,查看一个对象在内存中占用的字节。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
        
        // 获得NSObject实例对象的成员变量所占用的大小 >> 8
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        
        // 获得obj指针所指向内存的大小 >> 16
        // 返回指针所指向对象字节数
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
    }
    return 0;
}
打印结果

打印结果同时印证了上面的出的结论。

创建NSObject对象,在64位架构中分配8个字节的内存空间,用来存放一个成员isa指针。那么isa指针这个变量的地址就是结构体的地址,也就是NSObjcet对象的地址。
假设isa的地址为0x100400110,那么上述代码分配存储空间给NSObject对象,然后将存储空间的地址赋值给objc指针。objc存储的就是isa的地址。objc指向内存中NSObject对象地址,即指向内存中的结构体,也就是isa的位置。

那么NSObject中的其它属性和方法难道不占用内存吗?存在了那里?
当然占用内存,这个问题后面讲NSObject的对象分类再来解决。

二 、自定义类的内部实现

面试问题:自定义类的对象在内存中是如何分布的。
问题:自定义People类,声明_num和_age,则该People对象在内存中占几个字节?

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

/* Person类 */
@interface People : NSObject{
    @public
    int _num;
    int _age;
}
@end

@implementation People

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        People *p = [[People alloc] init];
        p ->_num = 4;
        p ->_age = 5;

        NSLog(@"%@",p);
        
    }
    return 0;
}

我们先自定义People类,通过转化为C++文件,可以查看People的底层实现:

People在底层的实现
struct People_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _num;
    int _age;
};
我们假设 struct NSObject_IMPL NSObject_IVARS;相等于 Class isa;
那么People在底层的实现即为
struct People_IMPL{
    Class isa;
    int _num;
    int _age;
};

结构体People_IMPL有3个成员变量,分别是isa,_num,_age,而我们知道isa是一个指针类型,在64位架构中占8个字节,_num和_age分别为int类型,在64位架构中分别占4个字节,那么People_IMPL结构体则占16个字节,因此People对戏那个占16个字节。

下面用代码打印,查看People对象在内存中占几个字节。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        People *p = [[People alloc] init];
        p ->_num = 4;
        p ->_age = 5;
        
        NSLog(@"======%@",p);
        
        // 1.通过OC的方式,分析占用内存
        NSLog(@"num= %d   age=   %d",p->_num,p->_age);

        // 2.通过结构体,分析占用内存
        // 把OC对象转为底层的结构体(相当于把指向OC对象的指针,转化为指向结构体的指针)
        struct People_IMPL *pImpl = (__bridge struct People_IMPL *)p;
        NSLog(@"num=   %d    age=  %d",pImpl ->_num,pImpl ->_age);

        // 3. 通过runtime机制,查看占用内存
        NSLog(@"%zd  %zd",
              class_getInstanceSize([NSObject class]),
              class_getInstanceSize([People class]));
    }
    return 0;
}
分析内存

通过打印结果可知,num和age为int类型,分别占4个字节,而NSObject占8个字节,所以People对象一共占16个字节,而最后打印的结果也为16个字节,所以我们前面在C++中的分析成立。

三、窥探内存结构

方式一:先断点,选中断点对象p,右键点击view Memory of "*p"


通过断点查看内存

通过打印结果,可以分析出
方式二:通过LLDB指令
lldb是xcode自带的调试器,简单介绍几个指令

print、po 打印
lldb读取内存
memory read/数量 格式 字节数 内存地址
简写 x/数量 格式 字节数 内存地址
格式 x是16进制,f是浮点,d是10进制
字节大小 b:byte 1字节,h:half word 2字节
w:word 4字节,g:giant word 8字节
根据地址修改内存中的值 : memory write 地址

image.png

四、OC对象的分类

OC对象主要分为3类,分别是instance对象(实例对象)、class(类对象)、meta-class(元类对象)。

1. instance对象

instance对象:就是通过类alloc初始化的对象,每次都会产生新的instance对象;

NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
obj1、obj2即为NSObject的instance对象,它们为两个不同的对象,分别占据着两个不同的内存空间

创建instance对象

People * obj1 = [[People alloc] init];
obj1->_age = 3;//0x10044b190

People * obj2 = [[People alloc] init];
obj2->_age = 4;// 0x10044b1e0
分析People的存储信息

通过上面的,我们可以看出,obj1、obj2分别占据着两个不同的内存
instance对象在内存中存储的信息包括

1、isa指针
2、其他成员变量

2. class对象
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

/* Person类 */
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

/* Person类 */
@interface People : NSObject{
@public
    int _age;
}
@property (nonatomic,assign) int num;
- (void)getPeopleClassMethod;
+ (void)addClassMethod;
@end

@implementation People
- (void)getPeopleClassMethod{
    
}
+ (void)addClassMethod{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        People *obj1 = [[People alloc] init];
        obj1->_age = 3;
        [obj1 getPeopleClassMethod];
        [People addClassMethod];
        
    }
    return 0;
}

Class对象在内存中存储的信息主要包括
1、isa指针
2、superclass指针
3、类的属性信息
4、类的对象方法信息
5、类的协议信息
6、类的成员变量

NSObject *object1 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [NSObject class];
// runtime
Class objectClass3 = object_getClass(object1);

NSLog(@"%p %p %p", objectClass1, objectClass2, objectClass3);
打印地址

说明它们是同一个对象,每个类在对象中有且只有一个Class对象

3、meta-class对象
 Class objectClass = object_getClass([NSObject class]);
 NSLog(@"%p", objectClass4);

objectClass是NSObject的meta-class对象(元对象)
1、isa指针
2、superclass指针
3、类的类方法信息
meta-class的对象和class对象的内存结构是一样的,但是它们的用途不一样

补充几点:

1、查看Class对象是否为meta-class对象
BOOL result1 = class_isMetaClass([NSObject class]);

Class objectClass4 = object_getClass([NSObject class]);
BOOL result2 = class_isMetaClass([objectClass4 class]);
NSLog(@"result===%d=======%d",result1,result2);

2018-08-07 15:52:19.165447+0800 OC对象的本质-自定义类02[3428:227786] result===0=======1

2、上面提过NSObject中的其它属性和方法难道不占用内存吗?存在了那里?
相信到这,我们已经知道了答案。
成员变量的具体值存放在instance对象。
对象方法,协议,属性,成员变量信息存放在class对象。
类方法信息存放在meta-class对象。

有些地方可能我还没有被理解透彻,望指点!

相关文章

网友评论

  • 不够果断是种癌:总结的挺好的
  • eagleyz:通过打印结果可知,num和age为int类型,分别占4个字节,而NSObject占8个字节,所以People对象一共占16个字节,而最后打印的结果也为16个字节,所以我们前面在C++中的分析成立。这一部分,感觉是有问题的。我试验了一下,当没有任何属性的时候,占用8个自己,有一个int属性时候占用16字节,有2个int 属性时候,也是16字节,当有3个int 类型时候,占用24个字节。
  • coderhlt:厉害了啊
    大兄弟

本文标题:OC底层:OC的本质

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