iOS类的底层探索(上)

前面我们已经学习了对象相关内容,如alloc的流程对象的内存分配,以及对象ISA的初始化等;明确了OCNSObject,与c\c++层中objc_object的对等关系;Class的定义为objc_class *类型。接下来,深入探索类的结构

一. isa 指针分析

对象的初始化过程中,学习了对象isa的初始化,并且isa中的shiftcls指向了对象所对应的类。

通过以下案例我们可以再次验证这一点,即对象内存结构的前八个字节是对象的isa,并且指向了p对象类LGPeron。见下图:

1.元类

在前面我们已经知道objc_class继承自objc_object,万物接对象,也就是说,类也有一个isa指针,那么思考以下,类的isa指向什么呢

接着上面的案例,打印LGPerson类中的isa指针所指向的内容,发现也是LGPerson!难道类isa指向自己吗?

不是,我们发现对象isa所指向的LGPerson类的地址为0x0000000100008368,而LGPerson类isa所指向的LGPerson地址为0x0000000100008340,说明这是两个不同的类!见下图:

这里引入一个元类的概念,元类的定义和创建都是由编译器自动完成!可以理解为,为类的方法找到一个归属!

  • 总结

对象isa -> 类,类isa -> 元类。

2.根元类

在上面的案例中,我们通过类对象isa找到了元类对象元类既然也是对象,那么它的isa又指向谁呢?

依然是上面的案例,我们打印LGPerson元类isa的指向,结果是NSObject!?见下图:

验证一下:NSObject类的地址LGPerson元类isa所指向的NSObject是否相同?见下图

从打印结果发现,并不相同!那么NSObject类isa又指向什么呢?

从结果中可以发现,NSObject类isa指向NSObject元类LGPerson元类isa也指向NSObject元类。因为NSObject是所有类的根类,所以将NSObject元类称为根元类

那么根元类isa又指向哪里呢?见下图:

通过打印根元类的内存空间,发现根元类isa指向了自己。

  • 总结

元类isa -> 根元类NSObject isa -> 根元类根元类 isa -> 自己

3.isa指针分析总结

补充:创建一个LGTeacher类,继承LGPerson类,同样按照上面的分析方式,发现LGPerson类isa指向的是根元类LGTeacher元类isa也指向根元类

通过上面的isa分析,可以得出isa的走位图:

二.superclass走位

在进行superclass走位分析之前,先要确定superclass指针所在位置,查看objc_class源码实现:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass; // 第二个8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    …… 省略
}
复制代码

通过上面的源码可以确定,superclass指针在类的第二个8字节中。

1.类superclass

引入一个案例LGTeacher继承自LGPersonLGPerson继承NSObject,以我们现有的知识储备,很容理解superclass的走位,即:LGTeacher superclass -> LGPersonLGPerson superclass -> NSObjectNSObject superclass -> nil

验证一下:

从输出结果看,和我们的设想是一样的。

2.元类superclass

类的superclass我们很熟悉,也很好理解,那么元类的superclass走位是怎样的呢?

同样的思路,首先找到LGTeacher元类,然后根据元类superclass开始分析。

从上面的流程可以发现,LGTeacher元类superclass指向LGPersonLGPersonsuperclass指向NSObjectNSObjectsuperclass指向NSObject根类。通过验证可以确定,这里的LGPerson元类,并且其superclass指向的是NSObject元类

  • 总结

LGTeacher元类 superclass -> LGPerson元类LGPerson元类 superclass -> NSObject元类NSObject元类 superclass -> NSObject类

3.superclass总结

根据上面的分析,可以确定以下superclass走位图:

  • isasuperclass总结,这里使用苹果官方的一走位图,将isasuperclass放在一起

补充:类、元类、根元类都是唯一的。

三.类结构分析

在进行类的结构分析前,需要明确的是OC层c\c++层的对应关系。见下图:

  • OC层,万物皆对象,大部分的类均继承自NSObject
  • 使用clang查看源码,定义的别名NSObjectobjc_object类型。
  • c\c++层,类的定义为objc_class,继承自objc_object,与OC层一致,万物皆对象。

1.类的结构初步分析

libObjc.A.dylib库中,全局搜索obj_class,得到其源码实现:

类中包含四个重要的属性,Class ISAClass superclasscache_t cacheclass_data_bits_t bits

  • isasuperclass前面已经做了分析,这两个指针确定了类之间的关系。
  • cache中存储了运行中的一些缓存信息,比如消息缓存。从objc_class提供的方法可以看出,在获取一些数据时,优先从缓存中获取,如果没有缓存,则从bits中获取。这样设计的目的是保证响应速度
  • bits,类中相关的数据信息都存储在了bits中。

2.cache_t cache

# oc类底层cache_t详解 本篇文章对cache_t进行了详细解析。

3.class_data_bits_t bits

class_data_bits_t是一个结构体,objc_class为其提供了两个重要的方法,data()setData()

    // objc_class - 类
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    // class_data_bits_t - rw
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
复制代码

在类的实现过程中,会创建一个class_rw_t,会将MachO文件中的class_ro_t数据拷贝一份到class_rw_t中,并设置到类中。在class_rw_t中提供了一些方法,可以获取类的属性、协议、分类、方法等。

四.类结构探索

引入一个案例,来探索类的结构,定义了LGPerson类,包括了两个属性,一个``成员变量,一个对象方法和一个类方法,见下面代码:

@interface LGPerson : NSObject {

    NSString *_hobby;

}


@property (nonatomic ,copy) NSString *name;

@property (nonatomic ,assign) int age;


- (void)instanceMethod;

+ (void)classMethod;


@end

@implementation LGPerson

- (void)instanceMethod {

    NSLog(@"%s",__func__);

}

+ (void)classMethod {

    NSLog(@"%s",__func__);

}

@end
复制代码

设置断点,运行程序,获取LGPerson类的地址,并答应类的内存结构。见下图:

很显然0x0000000100008230为指向元类的isa0x00000001007f8140superclass,那么如果要探索bits数据,应该查看那部分地址呢?

从上面的源码结构可以知道,isa占用8字节superclass占用8字节cache占用16字节,所以只要类地址平移32字节即可获取bits的首地址!

其中类的首地址为0x100008258,那么bits首地址 = 0x100008258 + 0x20。 通过上面的分析,将bits首地址强制转换为class_data_bits_t *即可成功获取class_data_bits_t数据结构。见下图:

此时bits不等于空,所以类中已经存储了相关的数据信息。继续探索,我们上面已经说明,class_data_bits_t结构体中提供了data()方法,用于获取class_rw_tclass_rw_t是在类初始化过程中已经被创建了,并且class_rw_t的相关数据来自MachO文件ro数据!

下面获取class_rw_t内容:

我们已经探索到了类的class_rw_t中,在这里我们可以获取相关的地方、属性等内容。

1.探索方法

查看源码可知,在class_rw_t中提供了const method_array_t methods()方法,在这里寻找突破口。

至此获取到了method_list_t,并且里面有count = 6个方法, 是哪6个方法呢?只要便利该列表就可以拿到相关方法了。打印输出第一个元素:

和我想的不一样,为空?查看method_t的源码实现,内部有一个big结构体,该结构体包括了方法编号SEL方法type encoding,和方法实现

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
}
复制代码

打印输出big数据,见下图:

成功输出了6个方法!其中nameage属性会自动生成get\set方法,但是没有类方法,全是对象方法!!!类+classMethod()方法在哪里呢? cxx_destruct 这个多出的函数是什么呢?

2.探索属性

和探索方法一样的方式,通过调用class_rw_t中的properties()方法,可以获取属性列表。运行结果如下:

成功找到了nameage两个属性,那么成员变量subject放在哪里呢,name和age对应的_成员变量又放在那呢?cxx_destruct 根据C++的知识像是析构函数,用来释放结构体内存。

3.class_ro_t谜底

走读class_rw_t的源码,并没有发现存储变量的相关属性,也没有获取变量的相关方法,但是在const class_ro_t *ro()方法获取的class_ro_t结构体中有一个属性const ivar_list_t * ivars;,并且是一个常量!说明在编译之初已经确定,运行时也不会修改!

下面从ro()方法中寻找突破口!见下图:

class_ro_t中,包括方法列表、协议列表、变量列表等,成员变量是否存储在这里呢?进去看看:

4.类方法

类方法呢?在前面的探索中,只找到了5个方法,类方法没有找到,同样在class_ro_tbaseMethods()中也没有找到类方法!类方法放在哪里呢?

跟踪消息慢速方法查找,发现此时的cls已经不是类了,而是元类

所以类方法存储在元类中!验证一下,找到LGPerson的元类,采用相同的方式进行指针平移,调用bits.data()获取class_rw_t,在methods()中成功获取了类方法+classMethod(),见下图:

  • 补充class_copyMethodList方法的使用

该方法的内部实现是const auto methods = cls->data()->methods();,可以获取类中的方法列表。当传入的分别是元类时,会获取不同的方法列表,从而也可以区分出对象方法类方法的存储位置的不同。

简单理解:对象是类的实例,类是元类的实例,方法都存储在各自的类中。

五.扩展内容

1.大端:高位字节存放内存的低地址段,低位字节存放内存的高地址段。

2.小端:高位字节存放内存的高地址段,低位字节存放内存的高低地址段。

0x12345678		0x10001		0x10002		0x10003		0x10004
 大端                    0x12            0x34             0x56            0x78
 小端                    0x78            0x56             0x34            0x12
复制代码

3.apple为什么要设计元类? 为了复用消息机制,可以快速发送。

objc_msgSend(消息的接收者 isa,消息的方法名,判断实例对象\类对象,判断实例方法/类方法)

objc_msgSend(消息的接收者 isa,消息的方法名)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值