NSObject 底层本质

栏目: IOS · 发布时间: 7年前

内容简介:一、OC 转 C/C++OC 的底层是通过 C\C++ 实现,所以 OC 代码编译过程一般是先将 OC 转为 C\C++ ,C\C++ 进一步转为汇编语言,最终转为机器代码。OC 的对象映射到 C\C++ 主要对应的是结构体,这里面的 “结构体” 并非 C 语言里面的结构体,而是 C++ 语言里面的结构体,而且这个概念仅限字面意思的结构体。严格来讲,其实struct关键字定义的是 类,跟 class 关键字定义的类除了默认访问权限的区别,没有区别。C++ 中的 struct 对 C 中的 struct 进
  • 一、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
}
</nsobject>

上述代码借助 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 地址相同。

NSObject 底层本质

三、对象内存大小

3.1 查看内存管大小

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

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 函数获得)。

四、OC 对象内存布局

4.1 简单对象

@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
    }
    return 0;
}

利用 xcrun 命名生成 C/C++ 代码,代码中会包含如下部分:

struct Student_IMPL {
    Class NSObject_IMP NSObject_IVARS;
    int _no;
    int _age;
};

其中NSObject_IMP 定义为:

struct NSObject_IMPL {
    Class isa; 
};

Student_IMP底层进而可转为如下结构,不难看出如果对象之间存在继承关系,最终子类转化对应的结构体会包含父类的结构体成分,且父类结构体成份在前。

struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};

为了进一步说明OC对象底层是结构体实现的,可以将OC对象强制转为结构体,发现代码依然正常运行。

Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
//使用__bridge 将 OC 转为 C
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);

结合上述代码,可以总结出Student对象的底层实现结构图:

NSObject 底层本质

内存布局大概是这样,stu 指针指向结构体地址,结构体地址即为首个成员变量的地址。

NSObject 底层本质

4.2 继承对象

如果是 Person 继承自 NSObject ,Student 继承自 Person 类,Person 中包含 age 成员变量, Student 包含 no 成员变量,则底层实现结构如下图。

NSObject 底层本质

表面上看 Person_IMPL 占据 8 + 4 = 12 个字节,但是 OC 中明确指出一个 NSObject 对象大小至少为 16 字节。从内存对齐角度分析来看,Person_IMPL结构体也至少为 16 字节,即结构体的大小必须是最大成员的倍数,即8 * 2 = 16。

表面上看 Student_IMPL 占据 16 + 4 = 20 个字节实际并非如此,应该为 16 字节。因为 Student_IMPL 中虽然包含Person_IMPL,但是在继承关系中,Person_IMPL中 age 对应的内存空间并未被使用,所以应该是 8 + 4 + 4 = 16 字节。

五、OC 对象内存大小

@interface MJPerson : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end
MJPerson *p = [[MJPerson alloc] init];
NSLog(@"%zd  %zd",class_getInstanceSize([MJPerson class]), malloc_size((__bridge const void *)(p))); // 24 32

p 对象对应的结构体为:

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

上述代码打印结果分别为 24 和 32,按照前面所说的结构体内存对齐原则来说,打印结果应该都为 24,即 8 的最小倍数, 8 * 3 = 24。class_getInstanceSize为结构体内存大小 8 + 4 + 4 + 4 = 24,但和实际情况确有一些出入。

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;
    }
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

结合 3.3 小结的源码来分析来看,class_getInstanceSize和alloc方法最终本质都是调用了alignedInstanceSize函数,其中alloc调用alignedInstanceSize函数中间使用到了 C 语言的 calloc函数,并且还涉及到一个变量extraBytes,但是此变量的值一般为 0,所以综合来看 malloc_size((__bridge const void *)(p))的值为 32 和calloc密不可分。因为在calloc函数中存在内存对齐一说(注意此内存对齐不等同于结构体内存对齐),即在堆区每次分配内存为 16 的倍数。如果比较感兴趣,可进一步查看 calloc函数源码 libmalloc

作者:ZhengYaWei

链接:https://www.jianshu.com/p/49947a53c4ed


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Filter Bubble

The Filter Bubble

Eli Pariser / Penguin Press / 2011-5-12 / GBP 16.45

In December 2009, Google began customizing its search results for each user. Instead of giving you the most broadly popular result, Google now tries to predict what you are most likely to click on. Ac......一起来看看 《The Filter Bubble》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具