在
OC
中,我们通过
alloc + init
方法去创建对象,那
alloc
和
init
到底做了什么事情。接下来,我们通过底层探索,去探究
alloc
和
init
的实现逻辑。
alloc方法的探索
首先来一段代码:
LLPerson *p = [LLPerson alloc];
LLPerson *p1 = [p init];
LLPerson *p2 = [p init];
通过po
命令打印三个指针指向的地址:
注:
p
是expr -
的缩写。它的工作是把接收到的参数在当前环境下进行编译,然后打印出对应的值。
po
即expr -o-
。它所做的操作与p
相同。如果接收到的参数是一个指针,那么它会调用对象的description
方法并打印。如果接收到的参数是一个core foundation
对象,那么它会调用CFShow
方法并打印。如果这两个方法都调用失败,那么po
打印出和p
相同的内容。
总的来说,po
相对于p
会打印出更多内容。一般在工作中,用p
即可,因为p
操作较少,效率更高。
是值相同一块内存空间的,同时我们将p
的name
属性设置值之后,p1
和p2
的name
属性也是相同的值,所以我们可以判断,alloc
方法创建了对象。
下面我们通过汇编来查看调用alloc
方法,它的内部是怎么样的:
为什么调用alloc
,会调用objc_alloc
方法呢?我们通过搜索源码找到fixupMessageRef
这个方法,在这个方法中我们可以看到,当调用alloc
方法时,是将msg
的imp
指向的objc_alloc
,所以调用了objc_alloc
这个方法。
// objc-runtime-new.mm
/***********************************************************************
* fixupMessageRef
* Repairs an old vtable dispatch call site. 修复一个旧的vtable调度调用站点
* vtable dispatch itself is not supported. Vtable调度本身不支持
**********************************************************************/
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) { // 如果是alloc,就使用objc_alloc这个imp
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
我们再通过源码去探索objc_alloc
方法发现内部调用的是callAlloc
这个方法。
// NSObject.mm
// Calls [cls alloc]. //调用alloc方法时会先调用这个
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
点击进入callAlloc
,在方法中,我们又通过objc_msgSend
调用了alloc
方法。
// NSObject.mm
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations. //调用[cls alloc]或[cls allocWithZone:nil],使用适当的快捷方式优化。
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));
}
查看源码调用流程,alloc
内部调用_objc_rootAlloc
,接着又会调用callAlloc
,相应的会走另一个分支。
// NSObject.mm
+ (id)alloc {
return _objc_rootAlloc(self);
}
// NSObject.mm
// Base class implementation of +alloc. cls is not nil. //+alloc的基类实现. cls不是nil
// Calls [cls allocWithZone:nil]. //调用[cls allocWithZone:nil]
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
即接下来会走_objc_rootAllocWithZone
这个方法:
// objc-runtime-new.mm
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter //__OBJC2__下的allocWithZone忽略zone参数
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
在_objc_rootAllocWithZone
方法的内部调用的是_class_createInstanceFromZone
这个方法,并且在这个方法里面返回了obj
对象:
// objc-runtime-new.mm
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch. //注意:这个函数写得很仔细,所以fastpath没有分支。
**********************************************************************/
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; //返回了对象obj
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
所以我们可以总结alloc
的方法调用栈:
alloc --> objc_alloc --> callAlloc --> objc_msgSend --> alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone
并且可以通过读取寄存器中的值,断定是LLPerson
类调用的对应的方法。
注:
register read xx
读取寄存器中的值
init方法
通过对alloc
方法的探索,我们发现alloc
方法调用之后,就已经生成了obj
对象,那init
方法呢?我们从源码中可以搜索到init
方法:
// NSObject.mm
// init方法只返回了obj
- (id)init {
return _objc_rootInit(self);
}
// NSObject.mm
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function. //在实践中,很难依赖于这个函数。
// Many classes do not properly chain -init calls. //许多类没有正确地链接-init调用。
return obj;
}
所以init
方法并没有做任何实质性的操作,只是返回了对象。这么做,是为了提供工厂模式,继承自NSObject
的类可以在init
中初始化成员变量,保证对象已经创建。
_class_createInstanceFromZone方法的探索
从alloc
方法中我们可以得出,_class_createInstanceFromZone
这个方法就是创建对象的真实方法,所以接下来,我们就来探索_class_createInstanceFromZone
这个方法是如何创建对象的。
首先就是计算对象的内存大小
size = cls->instanceSize(extraBytes); // 计算对象的内存大小
// objc-runtime-new.h
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. // 至少16个字节
if (size < 16) size = 16;
return size;
}
从代码中可以看到,对象至少会分配16
个字节内存。对于NSObject
对象,它内部只有一个isa指针,占用8
个字节,但是系统还是会为它分配16
个字节。
同时,在计算对象内存的时候,会进行内存对齐:
// objc-runtime-new.h
// Class's ivar size rounded up to a pointer-size boundary. //类的ivar大小四舍五入到指针大小边界。
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// objc-os.h
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
// 64位系统 8字节对齐 32位系统 4字节对齐
/**
(x + 8 - 1) / 8 * 8 可以8字节对齐
(x + 8 - 1) >> 3 << 3 位运算计算更快
系统方法是 (x + 7)& ~7
*/
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
// 64位系统 8字节对齐 32位系统 4字节对齐
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
// 16字节对齐
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
通过源码我们可以得出,在计算对象内存时,内存对齐方式是以8
字节对齐的。
计算完对象的内存,接下来就通过calloc
方法为对象分配内存空间,即obj = (id)calloc(1, size);
。在calloc
方法中,在分配内存空间的时候,是以16
字节对齐的方式分配的,所以最后obj
对象分配的内存是16
的倍数。
对象的本质
分配完内存空间之后,我们就去要去探究对象的本质是什么?
我们在main.m
文件中创建LLTeacher
,如下:
#import <Foundation/Foundation.h>
@interface LLTeacher : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
+ (void)test1;
- (void)test2;
@end
@implementation LLTeacher
+ (void)test1 {
}
- (void)test2 {
}
@end
然后通过clang -rewrite-objc main.m
编译main.m
文件生成main.cpp
文件。
我们从main.cpp
文件中摘除相关内容:
......
struct NSObject_IMPL {
Class isa;
};
......
#ifndef _REWRITER_typedef_LLTeacher
#define _REWRITER_typedef_LLTeacher
typedef struct objc_object LLTeacher;
typedef struct {} _objc_exc_LLTeacher;
#endif
extern "C" unsigned long OBJC_IVAR_$_LLTeacher$_name;
extern "C" unsigned long OBJC_IVAR_$_LLTeacher$_age;
struct LLTeacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
};
......
我们在类中添加了两个方法,两个方法都没有在结构体中。
我们可以得出对象的本质就是结构体,这个结构体包含isa
和成员变量的值。