在我们iOS开发且使用oc语言开发中,我们创建对象的既可以使用new,也可以使用alloc和init;但是我们常用的一般都是alloc和init,在我们使用这个创建对象时,我们是否会有疑问?alloc和init做了什么事情?接下来我们来探索一下alloc的流程分析。
示例代码:
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@ - %p - %p",p1,p1,&p1);
NSLog(@"%@ - %p - %p",p2,p2,&p2);
NSLog(@"%@ - %p - %p",p3,p3,&p3);
在上面的代码当中,我们创建了一个person类对象,其中p1只进行了alloc,p2,p3在p1的基础上进行了init。
在我们执行代码之后,得到下面的结果:
2020-09-05 20:17:48.224166+0800 alloc探索[3152:142267] <Person: 0x600000a986f0> - 0x600000a986f0 - 0x7ffee00b3118
2020-09-05 20:17:48.224434+0800 alloc探索[3152:142267] <Person: 0x600000a986f0> - 0x600000a986f0 - 0x7ffee00b3110
2020-09-05 20:17:48.224586+0800 alloc探索[3152:142267] <Person: 0x600000a986f0> - 0x600000a986f0 - 0x7ffee00b3108
从打印的结果可以了解到,p1,p2,p3都为同一个对象,他们所在的内存空间也是一样的,但是他们的地址是连续的且都占8bit。
也就可以得出,三个对象所指向的存储空间都为同一个,在这个存储空间中,三个对象以堆栈的方式连续占用8bit空间。
得到了上面的结果之后,我们还是没能知道alloc和init到底做了什么,我尝试的去点击alloc方法,却始终无法查看他做了什么。
那么我们如何查看alloc的源码实现呢?
下面有三种方式可以查看alloc源码在哪个动态库中:
第一种,下断点:
首先在p1处下断点,然后执行,执行之后,按住control和下图的图标

在点击几下之后,跳转到了如下图所示之后,在点击一下, 在左上角就可以看到alloc所在的动态库在libobjc.A.dylib中(注意:这个动态库的查看需要在真机上执行才能查看,由于我身边暂时没有真机,无法用图片表示)

第二种:在xcode左下角的+号出点击symbolic Breakpoint下断点,输入alloc,然后执行,断点卡在p1处,点击下一步,就会跳转到下图所示的界面处,就可以看到alloc所在的动态库是在libobjc.A.dylib中,这一步可以不用在真机环境下就可以查看到

第三种:通过汇编的方式
首先在p1处下断电,然后运行,接下来点击下图所示的选项。

再点击完成之后,就可以看到下图的汇编代码,例如objc_alloc、objc_msgSend等方法。

然后再按第一种方法的control+向下的箭头,同样的出现了像第一种方法的界面,再点击一下,就可以在左上角看到alloc所在的动态库(同样的是在真机环境下)。

在得到alloc所在的动态库之后,我们就可以在苹果官网macOS 10.15.6 Source去找到我们所需要的代码,我这边的版本是objc4-781最新源码。
如何让这个最新的源码能够执行,请参考文章
接下来,我们就可以在代码中查看我们需要的代码。
搜索alloc {,就会得到下图所示的样子:

当我们定位到alloc方法后,我们如何去查看源代码呢?
我们首先通过点击alloc中的方法,一个一个点进去:
_objc_rootAlloc,callAlloc.
当我们点击到callAlloc方法后,就存在if判断了,那么我们如何知道程序执行哪一步呢?
我们回到之前的代码,在代码中添加三个断点:_objc_rootAlloc,callAlloc,_objc_rootAllocWithZone。
在程序执行时,先将这三个断点取消掉(防止其他alloc对p1对象的创建进行干扰),在程序在p1处停下后,在选中三个断点,一步一步执行,最后程序执行的过程为:
1、

2、

3、

那么我们会发现,callAlloc方法不执行吗?
那当然不是,下一步我们来介绍一下编译器优化;
我们既然知道程序最后的执行方法为_objc_rootAllocWithZone,那么我们点击这个方法,再点击_class_createInstanceFromZone方法
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;
// 1:要开辟多少内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2;怎么去申请内存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3: ?
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);
}
最后得到三个重要的方法:
1、
要开辟多少内存空间
cls->instanceSize
2、
怎么申请内存
calloc
3、
obj->initInstanceIsa
那么这三个方法分别做了什么呢?为什么这么重要呢?
首先拿到我们配置好的objc4-781源码,创建一个person类,在main函数中创建一个person类,在p1中打上断点,然后执行程序;我们一步一步点击之alloc的方法,点击到_class_createInstanceFromZone中,在cls->instanceSize打上断点,然后执行下一步,我们可以清楚的看到程序跳转到了size方法处,然后我们点击instanceSize方法,里面有一个fastpath的if判断,在此处打断点,继续下一步,程序依然会跳转到if处,我们点击fastInstanceSize方法,里面依然有一个if判断:
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);
}
}
接下来的流程分析我也就不再继续解释了,就直接写出结果:
首先一个重要的结果就是align16方法的作用:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
其作用是16位字节对齐(苹果早期的是8字节对齐,现在是16),16字节的好处很多,例如:防止一些野指针和返回错误,给程序预留空间,保持安全性等。
然后就是alloc的流程:
alloc->_objc_rootAlloc_callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone->(cls->instanceSize(计算出需要的内存空间大小),calloc(向系统申请开启内存,返回地址指针),obj->initInstanceIsa(关联到相应的类))
由此得出,alloc是开辟内存空间,那么init做什么事情呢?
- (id)init {
return _objc_rootInit(self);
}
看上面的代码,他返回的是自己.
new源代码:
+ (id)new {
return [callAlloc(self, false) init];
}
new的源代码其实就是将alloc和init结合起来,但是,使用new方法我们不能够自定义初始化,而init可以自定义;因此,我经常使用alloc来创建对象。
网友评论