MJiOS底层笔记--OC对象本质

栏目: Objective-C · 发布时间: 5年前

内容简介:小码哥iOS底层原理班--MJ老师的课确实不错,强推一波。基于C与C++结构体实现OC ==> C++ ==> 汇编 ==> 机器语言
MJiOS底层笔记--OC对象本质

小码哥iOS底层原理班--MJ老师的课确实不错,强推一波。

OC对象本质

基于C与C++结构体实现

OC语言如何被编译器编译:

OC ==> C++ ==> 汇编 ==> 机器语言

而在C++中只有 struct(结构体) 才能容纳不同类型的内容( 比如不同属性 )。

将Objective-C代码转换为C\C++代码

  1. clang -rewrite-objc OC源文件 -o 输出的CPP文件

将源文件转写成通用的cpp文件

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

通过Xcode将源文件转写成arm64架构下的iphoneos文件,文件内容比第一种要少

  1. 如果需要链接其他框架,使用-framework参数。比如-framework UIKit

NSObject的OC与C++定义

  • 在OC中的定义
@interface NSObject <NSObject> {
    Class isa;
}
复制代码
  • 转成C++之后的定义
struct NSObject_IMPL {
	Class isa;
};
复制代码

其中 isa 是指向 objc_class 结构体的 指针

// 指针
typedef struct objc_class *Class;
复制代码

而一个指针在64位系统中所占的内存为8字节

所以一个OC对象所占的内存至少为8字节

NSObject对象所占用内存的大小

上面的结论通过 class_getInstanceSize 函数也可以佐证:

#import <objc/runtime.h>

/*
获得NSObject实例对象的
`成员变量`
所占用的大小 >> 8
*/
NSLog(@"%zd", class_getInstanceSize([NSObject class]));


//runtime源码中
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());
}
复制代码

需要注意这个 word_align 返回的是内存对齐后的大小,以 unalignedInstanceSize (为对齐的)大小作为参数。

而对于 NSObject *obj 指针,我们有另一个函数可以查看其实际被分配的内存大小

#import <malloc/malloc.h>
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
复制代码

为什么8字节的结构体会被分配16字节

继续看runtime

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}


// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}


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 {
            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 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;
}
复制代码

alloc函数最终会根据 instanceSize 返回的 size ,然后使用 calloc(1, size); 函数去分配内存。

instanceSize 函数中, alignedInstanceSize 方法为成员变量所占内存大小( 上面已经贴过一次 ). extraBytes 参数( 据我所见 )都为0。

CoreFoundation 框架在 instanceSize 函数中硬性规定不足16字节的内存地址会被补成16位字节。

但实际上, NSObject 对象只使用了 8字节 用来存储 isa 指针

Student对象的本质

@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end
复制代码

重写成C++之后

struct Student_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _no;
	int _age;
};

struct NSObject_IMPL {
	Class isa;
};


//其实就是
struct Student_IMPL {
	Class isa; //8字节
	int _no; //4字节
	int _age; //4字节
};
复制代码

所以一个 OC对象的本质 实际上是一个包含了 所有父类成员变量 + 自身成员变量 的结构体

Student的内存布局及大小

可以通过Debug->Debug workflow->View momory查看指定地址的结构来查证

MJiOS底层笔记--OC对象本质

对于Student实例对象所占内存地址的大小,我们同样可以通过 malloc_size 函数来确定。

结果是16。8字节父类的isa指针、4字节_age的int、4字节_no的int。

MJiOS底层笔记--OC对象本质

当然如果有兴趣可以用 memory write (stu地址+8偏移量) 8 的方式,通过直接修改内存的方式对成员变量 _no 的值进行修改。

需要注意的一点是:

Student_IMPL 结构体中的 NSObject_IMPL 结构体就已经占据了16个字节。 _no_age 两个成员变量只是将 NSObject_IMPL 未利用的8个字节分别利用了而已。

内存对齐原则下的OC对象内存分配

alignedInstanceSize()函数的内存对齐

alignedInstanceSize() 函数会按照所有成员变量中内存最长的一个做内存对齐。比如

@interface Animal: NSObject
{
    int weight;
    int height;
    int age;
}
复制代码

实际上只需要 8+4+4+4=20 个字节长度即可,但是内存对其之后会返回 8*3=24


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

父与子的编程之旅

父与子的编程之旅

桑德 (Warren Sande)、桑德 (Carter Sande) / 苏金国、易郑超 / 人民邮电出版社 / 2014-10-1 / CNY 69.00

本书是一本家长与孩子共同学习编程的入门书。作者是一对父子,他们以Python语言为例,详尽细致地介绍了Python如何安装、字符串和操作符等程序设计的基本概念,介绍了条件语句、函数、模块等进阶内容,最后讲解了用Python实现游戏编程。书中的语言生动活泼,叙述简单明了。 为了让学习者觉得编程有趣,本书编排了很多卡通人物及场景对话,让学习者在轻松愉快之中跨入计算机编程的大门。 第 2 版新增内......一起来看看 《父与子的编程之旅》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

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

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具