序言
探索底层,我们开始从平时最经常用的创建对象的alloc & init
开始,为了能纯粹的单独研究alloc
或者init
到底在底层究竟干了什么,我们把他们两个单独拆开,并打印对象本身,分配的内存地址和栈内存看一下,如下图:
结论:
1、自定义类调用alloc类方法,分配的堆内存只有一个,init方法并没有分配内存
2、alloc分配的堆内存会与PSYPerson进行绑定
3、person、pers1、pers2栈内存同指向同一个堆内存
分析底层的三种方式
1、符号断点
按照图片标号顺序添加符号断点 | 在Symbol栏输入要下断点的符号 |
通过下符号断点跟进流程
2、源码跟进流程
下载要分析的底层源码 源码下载地址 如要下载objc源码,可依次选择:macOS
--> 10.15.6 --> command + F 搜索objc
--> objc4-787.1 --> 点击后面的下载按钮
得到源码可以做一些配置,可参考这里 mac 10.15下的objc4-779.1源码编译、objc4-756.2源码编译,使其可以编译运行,然后借助control + step in
,一步一步跟进。
3、汇编跟进流程
依次点击Debug
--> Debug W0rkflow
--> Always Show Disassembly
,下断点运行之后就可以看到其汇编实现过程。还可以通过下内存断点,一直跟进去。
lldb断点语法基本使用方法:
b + 内存地址
下断点
breakpoint list
列出所有断点列表
breakpoint delete +断点标号
可删除某一条/组断点
breakpoint delete
删除所有断点
breakpoint enable
启用所有断点
breakpoint enable + 断电标号
启用指定的断点
breakpoint disable + 断电标号
禁用指定的断点
小结:个人认为,三种探索底层的方式并不是独立的,应该是相互结合使用,进一步的验证、完善和总结,才能得到相对完善的论证结果。
alloc原理初探
1、符号断点方式探索alloc流程
首先禁用掉所有的断点,让程序跑到到 [PSYPerson alloc]
调用之前(因为应用在启动的时候,存在超多的 alloc
方法,包括 dyld
动态加载过程,禁用掉断点就是要过滤掉其他的 alloc
,单纯分析 PSYPerson
调用的 alloc
),重新启用alloc
这一个断点,然后继续执行,可以看到, [PSYPerson alloc]
方法会调用[NSobject alloc]
方法,因为PSYPerson
类本身并没有alloc
类方法,所以会跑到其继承的父类中查找alloc
。
如下图,可以知道在
NSObject
的alloc
的实现里面,调用_objc_rootAlloc
方法。添加符号断点_objc_rootAlloc
,继续执行,就跑到了_objc_rootAlloc:
方法,里面会有两个分支,分别是:class_createInstance
和objc_msgSend
,然后再下两个符号断点class_createInstance
和objc_msgSend
,继续执行看会走哪一个分支。以此类推,即可得到比较清晰的流程。_objc_rootAlloc:的调用
2、源码分析方式探索alloc流程
首先可以通过源码进行静态的分析,基本上可以得到一本基本的流程,但是有一个点静态分析可能比较麻烦,那就是一个方法有多个条件判断返回,或者条件执行调用的话,静态分析就很难得到结果,需要借助动态法分析(汇编断点、符号断点、源码跑起来一步一步调试分析)。
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
// 核心方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
id
_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);
}
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
3、汇编跟进方式探索alloc流程
利用底层探索方式三汇编跟进流程,Xcode调起汇编方式:Debug
--> Debug W0rkflow
--> Always Show Disassembly
,结合符号断点,或者内存断点进行跟进流程。
通过下符号断点:alloc
,lldb
执行c
即可断到libobjc.A.dylib +[NSObject alloc]: ---> b 0x1ac981638 _objc_rootAlloc
,然后我们主要关注关于b
和bl
指令,对后面的内存地址进行下内存地址,如下:
libobjc.A.dylib _objc_msgSend_uncached
得到libobjc.A.dylib _class_lookupMethodAndLoadCache3
,此时发现走的流程比较深了,跑到了Runtime消息转发的流程,查找IMP,所以及时刹车。
结合上面三种探索方式可以得到alloc的流程:
接下来我们再主要针对几个关键的函数进行分析:
方法_class_createInstanceFromZone
旧的方法在objc-class-old.mm:感兴趣的条友们可以自行研究一下
# define WORD_MASK 7UL
/***********************************************************************
* _class_createInstanceFromZone. Allocate an instance of the
* specified class with the specified number of bytes for indexed
* variables, in the specified zone. The isa field is set to the
* class, C++ default constructors are called, and all other fields are zeroed.
在指定的区域创建一个指定类的实例,通过指定字节的变量
isa被设置到类,会调用C++的构造方法
**********************************************************************/
id
_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);
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
现在使用的是在objc-runtime-new.mm文件中
调用:
OBJECT_CONSTRUCT_CALL_BADALLOC = 2
_class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 获取类的实例大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
// 在 __OBJC2__ 中已经忽略zone这个参数了,所以直接关注calloc函数
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 申请内存空间,size为大小
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 将类与内存关联绑定
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
// 返回C++构造类方法
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
// ********************** *************************
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// 最少16字节,16字节对齐
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
:像对象、类等结构体的数据在内存中是连续的,数据有长有短,CPU
为了提高速率,在读取数据的时是以的方式读取的,如果CPU不断的变换读取长度,对于性能势必会造成影响,所以就需要内存对齐。这就是典型的。又因为数据是连续的,为了不造成读取数据的时候造成读取混乱,提高安全性,所以是以8字节的倍数对齐,但是有不能无限大,浪费空间,这就是著名的。
:
(x + size_t(15)) & ~size_t(15)
x = 10 对应的二进制就是 0000 1010
(10 + 15) & ~15
25 & ~15
25 对应二进制0001 1001
15 对应二进制0000 1111
~15 对应二进制 1111 0000
25 & ~15 ===》 0001 1001
& 1111 0000
------------------------
0001 0000 = 16
init作用
通过源码可以知道,init直接将alloc的对象直接返回,看似什么都没干,其实这是工厂设计模式,为了在继承该类的时候可以重写该类,提供构造方法的入口,并且赋初值。
+ (id)init {
return (id)self;
}
总结:
看起来有些乱,不过没关系,如果再结合类和对象的内存结构进行分析,将会得到一个更加清晰的思路和认识。
网友评论