小码哥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
malloc()/calloc()函数的内存对齐
在对象实际创建时,先以alignedInstanceSize()
返回的大小作为参考。 然后将会根据bucket
的大小进行分配。这个bucket
是16的整数倍。
所以Animal
的实例对象实际上会被分配32个字节长度的内存地址。