iOS开发 搞懂runtime系列文章(一)OC对象的底层结构

前言

iOS开发中,runtime库作为底层基础设施,发挥着重要的作用,可以说,runtime赋予了OC这门语言灵魂,让OC这门语言变得动态化和极具灵活性,让开发者可以对代码本身进行编程,俗称元编程。万丈高楼平地起,只有明白了runtime底层的原理,才能在软件开发中游刃有余,避免隐秘的错误的发生。iOS开发中的很多知识点都和runtime息息相关,比如内存管理,弱引用,关联对象,分类,方法调用,继承等等。

(1)资源和工具:

runtime是一个开源的C++库,官方源码地址是:objc4
可编译调试版本地址:debug-objc

(2)将OC类重写为C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 *.m

为什么我们在分析OC代码的时候,总喜欢用clang将OC代码重写为C++呢?因为C++或者C语言中的结构体,直接就对应了内存的真实情况。比如OC的类,block等。

一 OC对象的底层数据结构

通过浏览runtime源代码,我们知道,有这么几个关键数据结构:

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits; 
}

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
}

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
    explicit_atomic<uintptr_t> ro_or_rw_ext;
}

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    const uint8_t * ivarLayout;
    const char * name;
    WrappedPtr<method_list_t, PtrauthStrip> baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

其中struct objc_object就表示OC对象的底层数据结构,所有OC对象,都是在堆空间开辟一块内存,在其中依次存放isa指针和其他成员变量。下面,我们通过debug来验证这个结论:

@interface Person : NSObject

@property(nonatomic,assign)int age;

@property(nonatomic,strong)NSObject *obj;

@end

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        Person *person = [Person new];
        person.age = 20;
        
        NSObject *obj = [NSObject new];
        person.obj = obj;
//断点
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

在这里插入图片描述
上图中,我们看到,$0代表person的内存地址,$1代表person的成员变量NSObject的内存地址,x/8g $0 表示打印person对象内存地址64字节的内存信息,我们可以看到,第一个8字节0x0100000100e31601为isa指针,第二个8字节是0x0000000000000014,对应十进制数据20,第三个8字节是0x0000600002168020,正好就是$1,即obj对象的内存地址。至此,验证了上述OC对象内存结构的结论。

二 OC对象的创建:

了解了OC对象的内存结构之后,我们追溯源码看一下OC对象的创建过程:[[Person alloc]init]
objc4下载到本地,在NSObject.mm中可以看到alloc的具体实现:

+ (id)alloc {
    return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
}

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);
}
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);
}

研读上述调用栈,我们知道,最终调用的是_class_createInstanceFromZone函数中的malloc_zone_calloc或calloc函数,熟悉C语言的都知道,这两个函数是分配堆内存空间的,且需要向它们传入size参数,这个size表示要分配多大的堆内存。size来自于哪里呢?来自于参数cls,这个cls来自于+alloc中的self,alloc是一个类方法,self自然指代类。所以size来自于类信息中,至此,我们可以这么理解,创建OC对象的时候,就是去类信息中拿到要给对象分配的内存空间的大小,然后分配内存空间,这块内存空间,就是OC对象。
由上面的追溯过程,我们引出了类信息的概念,那么这个类信息反映到代码或者内存结构上,是什么样的呢?下篇文章将介绍有关OC类的结构。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值