iOS底层原理实践--对象本质

2020-06-06  本文已影响0人  我叫小成成
前言

最近在学习底层原理相关知识,想着写点东西,一则加深理解,二则希望能和大家一起交流,扩展思路。😸但简书搜索 iOS对象本质,惊了,我这是有多过时,各种文章满天飞,瞬间不知如何下笔。但还是硬着头皮往下写....,毕竟别人的终归是别人的,做好自己就好,这是我的处女作,写的不好解释不恰当的,请大家见谅,并帮忙指出。

废话不多说,直接入正题(以下内容都基于arm64进行分析)

一、NSObject的本质是什么?让我们来解开它的神秘面纱
#import <Foundation/Foundation.h>

//创建一个动物类
@interface Animal : NSObject
@end

@implementation Animal
@end

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

clang -rewrite-objc main.m -o main.cpp
或者
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
分析:
xcrun -sdk iphoneos
使用xcode命令行工具xcrun指定sdk类型为iphoneos
clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
使用clang编译器,指定编译的架构为arm64,-rewrite-objc重写main.m文件,-o 输出文件为main-arm64.cpp
未指定架构和sdk类型的,则会编译出所有sdk类型(例:macos,ipados)和所有架构(例:armv7 -i386)的代码,编译自然会慢一些

生成编译后的main-arm64.cpp文件(摘录部分代码)

//NSObject编译后的结构
struct NSObject_IMPL {
    Class isa;
};

//Class为结构体指针类型
typedef struct objc_class *Class;

//Animal类编译后结构
struct Animal_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    }
    return 0;
}

从上面的代码可看出,NSObject编译后的结构为结构体类型,其中包含一个Class类型的isa指针,Animal编译后的结构也为结构体类型,其中包含一个NSObject_IMPL的结构体。分析到这里,我们可以确认,OC代码的底层实现是C/C++。

接下来让我们分析几个问题,来巩固我们上面说的知识

二、NSObject对象占多少内存?
//NSObject编译后的结构
struct NSObject_IMPL {
    Class isa;
};

//Class为结构体指针类型
typedef struct objc_class *Class;

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

先简单通过打印分析一下,按照刚才我们所知的NSObject编译后的结构体,可以知道他包含了一个指针(arm64架构下,指针所占的内存为8个字节)。class_getInstanceSize获取的是类成员变量所占用的大小,因此该结果应该为8个字节,上图malloc_size获取的是object指针所指向的内存大小,按结构分析应该也是8个字节才对,但是实际打印却是16,接下来让我们跟踪源码苹果官方源码来分析下为何class_getInstanceSize得到的大小为8,malloc_size得到的大小为16。

分析class_getInstanceSize

// May be unaligned depending on class's ivars.
//获取(unaligned)字节对齐前类的(ivars)成员变量的大小
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;  
 }
// Class's ivar size rounded up to a pointer-size boundary.
//获取类的(ivar)成员变量字节对齐后的大小
uint32_t alignedInstanceSize() const {
     return word_align(unalignedInstanceSize());
}
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

分析malloc_size

//大家可以下载源码自己跟踪,alloc调用的是allocWithZone,然后即可接着定位到如下函数
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

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

    size = cls->instanceSize(extraBytes);
    //从此处可知,size的大小通过Class的instanceSize获取
    if (outAllocatedSize) *outAllocatedSize = size;
    //后续代码不重要,省略
    .....
}

size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
         return cache.fastInstanceSize(extraBytes);
     }
     size_t size = alignedInstanceSize() + extraBytes;
     // CF requires all objects be at least 16 bytes.
     if (size < 16) size = 16;
     return size;
}

根据上面的源码追踪,我相信都能理解了,也自然也能准确的回答NSObject所占的内存了。

NSObject对象占多少内存?
我的答案:在arm64架构下,NSObject所占内存大小为16个字节。其中8个字节为NSObject对象中的isa指针所占用的字节,另外8个字节是系统为了更优的访问内存额外开辟的空间,不存放任何数据。

三、NSObject对象占用多少内存我们已经知道了,那下面几个对象呢,让我们一起来分析一下

@interface Animal : NSObject
{
    int age;//年龄
    int weight;//重量
}
@end

@implementation Animal

@end

@interface Dog : Animal
{
    int no;//编号
}
@end

@implementation Dog

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        Dog *dog = [[Dog alloc] init];      

        NSLog(@"%zd\n",class_getInstanceSize([Animal class]));
        NSLog(@"%zd\n",malloc_size((__bridge const void *)(animal)));

        NSLog(@"%zd\n",class_getInstanceSize([Dog class]));
        NSLog(@"%zd\n",malloc_size((__bridge const void *)(dog)));
    }
    return 0;
}
struct Animal_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int age;
    int weight;
};

struct Dog_IMPL {
    struct Animal_IMPL Animal_IVARS;
    int no;
};
#define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, ..., 256} */

总结:
iOS对象本质,讲到这里就结束了。之所以把我自己思考问题的过程写在文章里,是希望能够帮助大家在了解其他知识的时候,多一个思考的方向,也希望各位能提出疑问,我尽量查漏补缺。

上一篇 下一篇

猜你喜欢

热点阅读