美文网首页
Objective-C的本质(1)——一个NSObject对象占

Objective-C的本质(1)——一个NSObject对象占

作者: yangfei02821 | 来源:发表于2020-04-14 16:34 被阅读0次

(一)问题:一个NSObject对象占用多少内存?

需要储备的知识点:

  1. 熟悉OC代码的底层实现
    我们平时编写的OC代码,底层其实都是C/C++代码
    所以OC的面向对象都是基于C/C++的数据结构(结构体)来实现
  1. 将OC的代码转换成C/C++代码
    clang -rewrite-objc main.m -o main.cpp
    xcrun -sdk ipnoneos clang -rewrite-objc main.m -o main.cpp
    xcrun -sdk ipnoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
    clang -fobjc-arc -framework Foundation main.m -o main.cpp
  • xcrun:Xcode运行
  • iphoneos:苹果手机系统
  • arm64:不同平台支持的代码不一样
    Windows、mac、iOS
    模拟器(i387)、32bit(armv7)、64bit(arm64)
  • main.m :OC源文件名
  • main.cpp :输出的cpp文件
    支持ARC、指定运行时系统版本,比如
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    有时候报错:/usr/include/mach/machine/vm_param.h:35:2: error: architecture not supported
    error architecture not supported
    去除掉-arch arm64就可以,具体原因不明。

3.NSObject的底层实现

//OC中NSObject定义
@interface NSObject <NSObject> {
    Class isa  ;
}
//转成cpp后的NSObject定义
struct NSObject_IMPL {
    Class isa; // 8个字节
};
// Class的定义(为一个指针)
typedef struct objc_class *Class;

注:一个指针占4个字节。指针即为地址,指针占几个字节跟语言无关,而是跟系统的寻址能力有关,譬如:以前是16位地址,指针即为2个字节,现在一般是32位系统,所以是4个字节,以后64位,则就占8个字节。
因此isa指针在内存中占8个字节。

NSObject内存布局
//NSObject对象内存中只有一个isa指针,内存地址假如为 0x100400110
//生成的obj的地址就为0x100400110
NSObject *obj = [[NSObject alloc] init]; 
// 16个字节

//引入#import <objc/runtime.h>
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));

//引入#import <objc/runtime.h>  
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));

这里是OC源码地址
https://opensource.apple.com/tarballs/

(1)、通过查看class_getInstanceSize源码,得知class_getInstanceSize函数打印为8的原因;

//class_getInstanceSize源码
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

// 获得实力对象的成员变量所占用的大小
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

(2)、通过查看alloc分配内存源码源码,得知malloc_size函数打印为16的原因;


alloc分配内存源码
CF要求所有对象至少16字节

具体源码如下:

id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);//——————>点击进入方法
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
——————————————————————————————————————
// obj = class_createInstance(cls, 0);
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);//——————>点击进入方法
}
——————————————————————————————————————
//_class_createInstanceFromZone(cls, extraBytes, nil);
static __attribute__((always_inline)) 
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);//——————>点击进入方法
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
——————————————————————————————————————
//size_t size = cls->instanceSize(extraBytes);
//系统分配内存,获取当前实力对象的内存大小(只包含isa指针,为8个字节),如果小于16个字节,会直接分配16字节。
size_t instanceSize(size_t extraBytes) {
        //获取当前实力对象的内存大小(只包含isa指针,为8个字节)
        size_t size = alignedInstanceSize() + extraBytes;//——————>点击进入方法
        // CF 要求所有对象至少为16个字节。.
        //小于16个字节,会直接分配16字节。
        if (size < 16) size = 16;
        return size;
    }
——————————————————————————————————————
// size_t size = alignedInstanceSize() + extraBytes;
//class_getInstanceSize的方法也是调用的这个函数
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
NSObject内存布局

总结:1. 系统跟配了16个字节给NSObject对象(通过malloc_size函数获得)
2.但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

注:2个容易混淆的函数

//创建一个实例对象,实际上分配了多少内存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);

//创建一个实例对象,至少需要多少内存?
//类似sizeof(),区别为传入参数不同,sizeof为运算符,不为函数,是传入的类型为多大,编译时直接确定,比如传入person对象,显示是person的类型,指针的大小。class_getInstanceSize为函数,传递为类,显示传入类的大小
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);

例如:

struct MJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //8
    int _age;//4
    int _height;//4
    int _no;//4
}; // 计算结构体大小,内存对齐,24

@interface Person : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end

@implementation MJPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[MJPerson alloc] init];

        NSLog(@"%zd %zd",
              malloc_size((__bridge const void *)(p))); // 32
              class_getInstanceSize([Person class]), // 24
    }
    return 0;
}

分析:
1.malloc_size()为类对象动态分配的内存大小,是操作系统内存对齐之后的大小,补齐规则为16的整数倍。即如果一个类对象需要的内存大小为20,通过malloc_size()方法获取的大小为32,即16的倍数;(16字节原因:通过libmalloc源码获知,其中一种分配方式:操作系统提前规划好了为16个字节倍数的空间,最大为256,使用时,系统直接取出对应空间大小的内存)
2.class_getInstanceSize()方法获取的内存大小是结构体内存对齐之后的大小,补齐规则按照类的成员变量所占内存最大值的整数倍。即如果一个类对象需要的内存大小为20,最大成员为isa内存占用8字节,通过class_getInstanceSize()方法获取的大小为24,即8的倍数

(二)思考:一个Person对象、一个Student对象占用多少内存空间?

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8
    int _age; // 4
}; // 16 内存对齐:结构体的大小必须是最大成员大小的倍数

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 16
    int _no; // 4 存在于没有使用的那四个字节中
}; // 16

如图:


Person 对象和Student对象的C++代码
Person 对象和Student对象内存图片

答案:都为16字节。

相关文章

网友评论

      本文标题:Objective-C的本质(1)——一个NSObject对象占

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