iOS底层原理笔记 - OC对象的本质

124 阅读3分钟

NSObject的本质

先创建个demo,方便调试

编译过程:OC — C++ — 汇编语言 — 机器码;

main.m中,创建一个NSObject对象:

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

命令行将main.m转成c++代码:

没有指定架构,生成main.cpp文件(3.7M)

clang -rewrite-objc main.m -o main.cpp

指定arm64架构,生成main-arm64.cpp文件(1.8M)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64
.cpp

在main-arm64.cpp中搜索NSObject,找到NSObject_IMPL,IMPL-implementation(实现)

struct NSObject_IMPL {
	Class isa;
};

可见NSObject本质就是NSObject_IMPL的struct(结构体);

结构体里只有一个指向Class的isa指针,64位系统一个指针占8个字节(32位4个)

#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //获取NSObject实例对象大小:64位 占8个字节
        NSLog(@"%zd",class_getInstanceSize([NSObject class]));  //8
    }
    return 0;
}

#import <malloc/malloc.h>  //引入头文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //获取NSObject实例对象大小(实际占用的内存大小):64位 占8个字节
        NSLog(@"%zd",class_getInstanceSize([NSObject class]));  //8
        
        NSObject *obj = [[NSObject alloc] init];
        //obj指针所指向的内存大小(堆分配的内存大小)
        NSLog(@"%zd",malloc_size((__bridge const void *)obj));  //16
    }
    return 0;
}

🌰

创建继承NSObject的Person类:

@interface Person() {
    int _age;
    int _height;
    int _weight;
}
@end

同理转成c++文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o Person.cpp

查看Person的内存结构,父类的NSObject_IMPL占8个字节,int各占4个字节

struct NSObject_IMPL {
    Class isa;   //8
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;    //8字节
    int _age;       //4
    int _height;    //4
    int _weight;    //4
};//20个字节,内存对齐,应该是8的倍数,所以最终占24个字节

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        
        //结构体所占大小
        NSLog(@"%zd",sizeof(struct Person_IMPL)); //24
        //对象实际大小
        NSLog(@"%zd",class_getInstanceSize([Person class])); //24
    }
    return 0;
}

接下来我们看个更复杂的🌰🌰

Dog继承自Animal,Animal继承自NSObject

@interface Animal : NSObject
{
    int _age;
}
@end

@implementation Animal
@end
 

@interface Dog : Animal
{
    int _no;
}
@end

@implementation Dog
@end

转成c++,可以发现类对象是以结构体的形式存储在内存中的

struct NSObject_IMPL {
    Class isa;  //8
};

struct Animal_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;   //4
};

struct Dog_IMPL {
    struct Animal_IMPL Animal_IVARS;
    int _no;    //4
};

打印结果

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Animal *animal = [[Animal alloc] init];
        Dog *dog = [[Dog alloc] init];
        
        //实际占用多少字节 
        NSLog(@"实际占用 animal -- %zd, dog -- %zd",class_getInstanceSize([Animal class]),class_getInstanceSize([Dog class]));
        //实际分配多少字节 
        NSLog(@"实际分配 animal -- %zd, dog -- %zd",malloc_size((__bridge const void *)animal), malloc_size((__bridge const void *)dog));
    }
    return 0;
}

输出: 实际占用 animal -- 16, dog -- 16
      实际分配 animal -- 16, dog -- 16

一个完整🌰🌰🌰

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

@interface Animal : NSObject
{
    int _age;
    int _height;
    int _weight;
}
@end

@implementation Animal
@end


@interface Dog : Animal
{
    int _no;
}
@end

@implementation Dog
@end

struct NSObject_IMPL {
    Class isa;      //8
};

struct Animal_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;       //4
    int _height;    //4
    int _weight;    //4
};

struct Dog_IMPL {
    struct Animal_IMPL Animal_IVARS;
    int _no;        //4
};


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Animal *animal = [[Animal alloc] init];
        Dog *dog = [[Dog alloc] init];
        
        //实际占用多少字节  24
        NSLog(@"实际占用 animal -- %zd, dog -- %zd",class_getInstanceSize([Animal class]),class_getInstanceSize([Dog class]));
        //实际分配多少字节  32
        NSLog(@"实际分配 animal -- %zd, dog -- %zd",malloc_size((__bridge const void *)animal), malloc_size((__bridge const void *)dog));
    }
    return 0;
}

我们分析Animal实例对象中,NSObject_IMPL占8个字节,_age占4个字节,_height占4个字节,_weight占4个字节,所以Animal对象占20个字节。

在Dog实例中Animal_IMPL占20个字节,_no占4个字节,所以Animal实例占24个字节。

但是为什么系统分配了32个字节?

内存对齐原则:

1.实际所占内存大小应该是8的倍数,例如算出实际大小是24,补齐之后应该是24;

2.系统分配给对象的内存大小最少是16个字节,并且是16的倍数,例如实际对象占用内存24,但系统会分配32;

注意:

class_getInstanceSize是获取实例对象实际所占的内存大小;程序运行时获取;8对齐

malloc_size是获取系统分配给对象的内存大小;16对齐

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