MJiOS底层笔记--OC对象本质
栏目: Objective-C · 发布时间: 7年前
内容简介:小码哥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对象的本质(二)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Haskell School of Music
Paul Hudak、Donya Quick / Cambridge University Press / 2018-10-4 / GBP 42.99
This book teaches functional programming through creative applications in music and sound synthesis. Readers will learn the Haskell programming language and explore numerous ways to create music and d......一起来看看 《The Haskell School of Music》 这本书的介绍吧!
Base64 编码/解码
Base64 编码/解码
UNIX 时间戳转换
UNIX 时间戳转换