微信号:cocoachinabbs

介绍:CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务.

NSObject 底层本质

2018-11-05 08:00 ZhengYaWei

本公众号内容均为本号转发,已尽可能注明出处。因未能核实来源或转发内容图片有权利瑕疵的,请及时联系本号,本号会第一时间进行修改或删除。 QQ : 3442093904 


  • 一、OC 转 C/C++

  • 二、NSObject 对象内存布局

  • 三、NSObject 内存大小

  • 四、OC 对象内存布局

  • 五、OC 对象内存大小


一、OC 转 C/C++


OC 的底层是通过 C\C++ 实现,所以 OC 代码编译过程一般是先将 OC 转为 C\C++ ,C\C++ 进一步转为汇编语言,最终转为机器代码。OC 的对象映射到 C\C++ 主要对应的是结构体,这里面的 “结构体” 并非 C 语言里面的结构体,而是 C++ 语言里面的结构体,而且这个概念仅限字面意思的结构体。严格来讲,其实struct关键字定义的是 类,跟 class 关键字定义的类除了默认访问权限的区别,没有区别。C++ 中的 struct 对 C 中的 struct 进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。如:能包含成员函数、可以继承、可以实现多态。


通过 xcrun 命令可以将 OC 代码转为不同平台CPU下支持的 C\C++ 代码,如 OC 代码转为 arm64 架构 CPU 代码,对应的命令为:


xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件


二、NSObject 对象本质


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


点击可查看NSObject定义为如下,可以看出 NSObject 类中包含了一个 isa 成员变量。


@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}


上述代码借助 xcrun 命令生成的文件中包含如下代码,实际上NSObject的定义最终也是转为如下代码。


//其中 Class 的定义为:typedef struct objc_class *Class; 64位系统中,指针占据 8 个字节
struct NSObject_IMPL {
    Class isa; // 8个字节
};


NSObject *obj = [[NSObject alloc] init];的内存布局如下。alloc相当于为为右侧蓝色的结构体开辟一块空间,结构体中保存着 isa 成员,isa 成员的指针的地址相当于结构体地址空间,初始化成功后,结构体的地址赋值给 obj 对象,因此 isa 地址和 obj 地址相同。



三、对象内存大小


3.1 查看内存管大小


//#import   NSObject *obj = [[NSObject alloc] init];
  // 获得NSObject实例对象的成员变量所占用的大小 >> 8
  NSLog(@"%zd", class_getInstanceSize([NSObject class]));
  // 获得obj指针所指向内存的大小 >> 16
  NSLog(@"%zd", malloc_size((__bridge const void *)obj));


class_getInstanceSize方法可以获取实例对象的成员变量大小,即创建一个实例对象,至少需要多少内存。malloc_size方法可以获取对象指针所指向内存大小,即创建一个实例对象,实际上分配了多少内存。两个方法的区别具体可以看runtime底层源码观察其区别。OC 底层源码一般可在该网站查看


3.2 class_getInstanceSize 函数


class_getInstanceSize底层实现如下,其中英文注释很清晰的描述了该方法返回的是成员变量(Class's ivar)大小。


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


// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }


3.3 alloc 函数


OC 中的alloc在底层调用 runtime 的allocWithZone方法:


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;
}


id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _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 {
          //该处调用了C语言的 calloc 函数开辟空间,所以查看NSObject 对象的创建开辟空间大小应当依次为切入点,查看size 参数来源
            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;
}


上述代码调用了C语言的 calloc函数开辟空间,所以查看NSObject 对象的创建开辟空间大小应当依次为切入点,查看size参数来源。alignedInstanceSize()方法是 class_getInstanceSize 方法底层来源,所以下面代码中的extraBytes变量值实际为 0。非常值得注意的是,下述代码中明确指出CF requires all objects be at least 16 bytes.,即 CF 对象至少为 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.4 小结


综上,系统分配了 16 个字节给 NSObject 对象(通过 malloc_size 函数获得),但 NSObject 对象内部只使用了 8 个字节的空间,这8个字节主要用来存放 isa( 64bit 环境下,可以通过 class_getInstanceSize 函数获得)。


<