coderz blog

OC对象的本质

2021-03-31

OC 对象的本质

最近时间充足,打算再次对 Object-C 进行分析,而 Object-C本身绝对是优雅和美丽的,所以从这里开始,我们和Object-C来一场美丽的邂逅!

一、认识Object-C对象

Object-C本质

  • 我们平常编写的Object-C代码,底层其实都是C/C++代码,编译器再编译成汇编,最终形成机器语言
  • 对象、类 主要是基于C/C++结构体实现

那么一个Object-C对象在底层中如何布局的?通过分析不难发现其底层实现如下图

1
2
3
struct NSObject_IMPL{
Class isa
}

内存初窥

  • 一个Object-C对象会被分配不少于 16 bytes 的空间
  • 对象的指针地址为第一个成员的地址
  • 遵守内存对其原则

可能你对最小分配16字节大小的结论有疑问,可以点击 objc4/ 下载源码查看内部实现 。在 objc-class-old 文件的 _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) 方法中实现了下面规定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
void *bytes;
size_t size;

// Can't create something for nothing
if (!cls) return nil;

// Allocate and initialize
size = cls->alignedInstanceSize() + extraBytes;

/**
此处为官方硬性规定
*/
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;

if (zone) {
bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
bytes = calloc(1, size);
}

return objc_constructInstance(cls, bytes);
}

内存对齐又是怎么一回事? 看看官方说明一解疑惑

1
2
3
4
5
6
// Class's ivar size rounded up to a pointer-size boundary.
// alignedInstanceSize 返回对齐大小
// unalignedInstanceSize 未对齐之前大小
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}

二、Object-C对象创建原理

官方给出了两种创建对象的方式,方式一:通过 [[object alloc]init] ,方式二:通过 [object new] 下面我们用两种方式来新建一个 Person 对象一探究竟。

1
Person * person = [[Person alloc] init];

简单的一次调用,其背后原理为何?

我将调用 alloc 整个源码流程贴出方便大家比对

2.1 源码处理流程
  1. 先调用 objc_alloc ,此处会去调用 callAlloc 函数,注意后两个参数
1
2
3
4
5
6
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
  1. 那么跳转到 callAlloc 函数 系统又处理了什么逻辑呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations. // 算法优化
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //在 objc2中 系统会处理这些函数
/**
笔记
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
第一次 checkNil=true
allocWithZone=false,其默认就是 false

#define fastpath(x) (__builtin_expect(bool(x), 1)): x值越大表示为真的结果可能性更大
#define slowpath(x) (__builtin_expect(bool(x), 0)) x值越小表示为假的结果可能性更大
__builtin_expect:是 GCC提供的给我们的,目前在于允许我们将最有可能执行的分支告诉编译器。以达到对代码进行优化,减少指令跳转带来的性能下降。
*/

if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// 如果条件成立 调用 _objc_rootAllocWithZone
return _objc_rootAllocWithZone(cls, nil);
}
#endif

// No shortcuts available. //如没有最优捷径
if (allocWithZone) { //如实现了allocWithZone 方法 实现消息转达 allocWithZone 方法
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}

// 未实现 allocWithZone ,直接调用 alloc 方法
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
  1. 在第二步中存在两种调用关系,一种是存在最优化捷径 则直接调用 rootAllocWithZone 方法;一种则是在非 objc2 下 实现 allocWithZone 或者 alloc 函数。

    在源码中官方直接去调用 _class_createInstanceFromZone 函数申请存储空间有兴趣的同仁可以跟踪下源码看看官方怎么进行存储空间开辟的,这里你会对前面内存初窥中写道的结论有个深刻的认识。

    1
    2
    3
    4
    5
    6
    _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
    {
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
    OBJECT_CONSTRUCT_CALL_BADALLOC);
    }

    对于alloc方法 系统实现起来就显得不那么复杂了

    1
    2
    3
    4
    + (id)alloc {
    return _objc_rootAlloc(self);
    }

2.2 init 原理

分析完了 alloc 的流程,我们接着分析 init 的流程。相比于 alloc 来说, init 内部实现十分简单,先来到的是 _objc_rootInit ,然后就直接返回 obj 了。其实这里是一种抽象工厂设计模式的体现,对于 NSObject 自带的 init 方法来说,其实啥也没干,但是如果你继承于 NSObject 的话,然后就可以去重写 `initWithxyz 之类的初始化方法来做一些初始化操作。

1
2
3
4
5
6
7
8
// Replaced by CF (throws an NSException)
+ (id)init {
// 工厂设计模式
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}

三、总结

开发过程中探究源码会让我们对一项事务有不一样的认识,建议同仁多阅读源码。

Tags: iOS
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章