对象的底层探索(上)


OC中,我们通过 alloc + init方法去创建对象,那 allocinit到底做了什么事情。接下来,我们通过底层探索,去探究 allocinit的实现逻辑。

alloc方法的探索

首先来一段代码:

    LLPerson *p = [LLPerson alloc];
    
    LLPerson *p1 = [p init];
    LLPerson *p2 = [p init];

通过po命令打印三个指针指向的地址:
在这里插入图片描述

注:
pexpr -的缩写。它的工作是把接收到的参数在当前环境下进行编译,然后打印出对应的值。
poexpr -o-。它所做的操作与p相同。如果接收到的参数是一个指针,那么它会调用对象的 description 方法并打印。如果接收到的参数是一个 core foundation 对象,那么它会调用 CFShow 方法并打印。如果这两个方法都调用失败,那么 po 打印出和 p 相同的内容。
总的来说,po 相对于 p 会打印出更多内容。一般在工作中,用 p 即可,因为 p 操作较少,效率更高。

是值相同一块内存空间的,同时我们将pname属性设置值之后,p1p2name属性也是相同的值,所以我们可以判断,alloc方法创建了对象。
下面我们通过汇编来查看调用alloc方法,它的内部是怎么样的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么调用alloc,会调用objc_alloc方法呢?我们通过搜索源码找到fixupMessageRef这个方法,在这个方法中我们可以看到,当调用alloc方法时,是将msgimp指向的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和成员变量的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值