MJiOS底层笔记--OC对象本质
栏目: Objective-C · 发布时间: 5年前
内容简介:小码哥iOS底层原理班--MJ老师的课确实不错,强推一波。基于C与C++结构体实现OC ==> C++ ==> 汇编 ==> 机器语言
小码哥iOS底层原理班--MJ老师的课确实不错,强推一波。
OC对象本质
基于C与C++结构体实现
OC语言如何被编译器编译:
OC ==> C++ ==> 汇编 ==> 机器语言
而在C++中只有 struct(结构体)
才能容纳不同类型的内容( 比如不同属性
)。
将Objective-C代码转换为C\C++代码
-
clang -rewrite-objc OC源文件 -o 输出的CPP文件
将源文件转写成通用的cpp文件
-
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
通过Xcode将源文件转写成arm64架构下的iphoneos文件,文件内容比第一种要少
- 如果需要链接其他框架,使用-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查看指定地址的结构来查证
对于Student实例对象所占内存地址的大小,我们同样可以通过 malloc_size
函数来确定。
结果是16。8字节父类的isa指针、4字节_age的int、4字节_no的int。
当然如果有兴趣可以用 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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- NSObject 底层本质
- KVO 底层本质
- IOS 底层原理 类的本质--(2)
- IOS 底层原理 对象的本质--(1)
- iOS底层原理总结 - 探寻Runtime本质(三)
- 窥探iOS底层实现--OC对象的本质(二)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。