1、指针平移
在介绍类的内存结构之前先介绍一下指针平移,在后面会用到相关知识。

这张图的意思呢就是定义一个整型数组
intArray
,里面的元素分别为10
,2
,6
,4
,8
,可以利用intArray[0]
方式取到元素10
,其他的元素依次类推,这个大家都知道。还可以采用下面的方式:
简单解释一下:
int * p = intArray
就是定义一个指针p
指向数组intArray
,我们知道数组的地址就是首元素的地址,所以*p
取得值就是第一个元素,p+1
就是指针往后移动一位也就是指向第二个元素,*(p+1)
取得就是第二个元素,依次可以取到数组中所有的元素,这就是指针平移的用法。
2、class内存结构
class的指针平移跟上面讲到的平移一样,想要平移指针首先要知道class每个属性占的字节数,然后才能平移到我们想要的位置,我们先来回顾一下class的结构(818版本):

从定义可以看到
class
里面总共有4
个属性,分别是isa
、superclass
、cache
和bits
。
- 获取
isa
,从class
首地址开始取8
字节就可以了,因为isa
是一个指针,指针的大小固定8
字节。 - 获取
superclass
,从class
首地址平移8
字节后再取8
字节就可以了 - 获取
cache
,从class
首地址平移16
字节后再取cache
所占字节数就可以了 - 获取
bits
,从class
首地址平移16+cache所占字节数就可以了
从上面的分析知道如果知道cache的字节数,就可以通过平移的方式来获取class各个属性的值了。我们先看一下cache的大小
2.1 cache大小
cache是cache_t类型,看一下cache_t的定义(818版本):

这个定义里面有很多
static
类型的属性,static
类型属性是不存在结构体里面的,所以我们只需关注红色部分就可以了。其实就是两部分:
_bucketsAndMaybeMask
和union
(1)
_bucketsAndMaybeMask
是uintptr_t
类型,看一下定义:

就是无符号长整型,
8
字节。(2)
union
通过前面的介绍,里面的struct
和_originalPreoptCache
是共用内存的,我们只看一个就可以了,最简单的是_originalPreoptCache
,就是一个preopt_cache_t
指针,所以也是8
字节。
总结:通过分析我们知道了cache
的大小就是16
字节。
2.2 bits分析
class
里面属性我们已经知道isa
和superclass
,还剩cache
和bits
,cache
看名字就知道是缓存用的,所以接下来我们就先研究一下bits
。
用x/8gx
查看一下MlqqObject
内存结构:

在图中可以轻松找到bits的起始地址,我们在验证一下对不对,按照图中方法转化一下:

能正常打印出来是
class_data_bits_t
类型指针,说明我们移位没问题。
我们只拿一个指针没什么用,关键是要看指针里面指向的内容是什么,在objc_class的可以找到下面的方法:
class_rw_t *data() const {
return bits.data();
}
可以看到bits
有一个data()
方法,我们也可以调用,看看里面返回什么内容:

确实是返回了一个
class_rw_t
类型的指针,接着看一下这个指针的内容是什么
看到这里还是没能看到我们想看的内容,既然是类的结构,那总要看到跟类相关的方法、属性等内容吧。我们打印的$3变量是
class_rw_t
结构体,我可以在这个结构体里面找到相关的方法:1、获取method的函数:
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
2、获取properties的函数:
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
3、获取protocols的函数:
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
2.3 获取method,properties和protocols
在获取之前先来看一下MlqqObject定义的方法和属性:

2.3.1 获取methods
通过class_rw_t
源码我们知道里面有获取methods
的方法,既然有方法就好办了,我们来获取一下方法,通过变量$3
调用methods()
方法

得到变量$4
,可以看到$4
里面有list
,就是一个数组,我们用$4.list
取到里面的list
得到变量$5
:

这里解释一下:ptr
指的是指针,指向的类型是method_list_t
,所以我们可以强转一下类型:

打印一下$6
的值:

我这里用的是objc-818.2版本看到的是这个格式跟之前版本略有不同,这里面总共有6
个方法,可以通过变量$7取出来里面的值:

取出来变量$8
没有信息,但是可以看到是一个method_t
类型,搜一下源码可以看到method_t
定义中有很多方法,我们这里打印一下其描述信息看一下,先看一下方法:
objc_method_description *getDescription() const {
return isSmall() ? getSmallDescription() : (struct objc_method_description *)this;
}

在打印一下变量$9
的值:

这样我们就取出来第一个方法了,按着这步骤我们分别取一下其他的值:

这样可以将类里面的方法全部遍历出来了。但是我们没有看到
+ (void)hobbyTest
这个类方法。超过数组数量就会发生越界。
2.3.2 获取properties
我们在来看看获取properties
方式,前面跟获取methods一样,只是在将methods()方法换成properties()方法,其他的获取方式都是一样的:

这样我们就可以看到所有的属性了,但是我们没有看到
hobby
这个成员变量。
2.3.2 获取protocols
这个跟上面两个获取的方式一样,大家可以自己动手试试。
总结:这篇要介绍的已经介绍完了,还有两个问题:
1、+ (void)hobbyTest
这个静态方法我们没看到存在哪里
2、hobby
这个成员变量我们也没看到存在哪里
2.4 获取成员变量
其实成员变量是存在结构体class_rw_t
的ro
里面,获取方式如图:

获取结构体
class_ro_t
的ivars
:

这样就可以取到类的成员变量了
2.5 获取类方法
通过上面的分析,我们在MlqqObject.class没有找到+ (void)hobbyTest
这个类方法,那么我们可以看看其元类里面有没有,根据前面的知识先打印其元类的内存结构:

接下来就是获取
methods
,参考2.3.1
就不一步步展示了,直接看结果:
这里验证了类方法是存在元类里面的
2.6 类方法归属分析
为了探索类方法的归属问题,我们需要两个方法:
方法1:获取类及元类的实例
方法
void instanceMethod_classToMetaclass(Class mClass){
const char *className = class_getName(mClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(mClass, @selector(bitsTest));
Method method2 = class_getInstanceMethod(metaClass, @selector(bitsTest));
Method method3 = class_getInstanceMethod(mClass, @selector(hobbyTest));
Method method4 = class_getInstanceMethod(metaClass, @selector(hobbyTest));
NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
方法2:获取类及元类的类
方法
void classMethod_classToMetaclass(Class mClass){
const char *className = class_getName(mClass);
Class metaClass = objc_getMetaClass(className);
Method method5 = class_getClassMethod(mClass, @selector(bitsTest));
Method method6 = class_getClassMethod(metaClass, @selector(bitsTest));
Method method7 = class_getClassMethod(mClass, @selector(hobbyTest));
Method method8 = class_getClassMethod(metaClass, @selector(hobbyTest));
NSLog(@"%s-%p-%p-%p-%p",__func__,method5,method6,method7,method8);
}
2.6.1 实例方法
我们在main函数里面先调方法1:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
MlqqObject * objc = [[MlqqObject alloc] init];
Class mClass = object_getClass(objc);
instanceMethod_classToMetaclass(mClass);
NSLog(@"Hello, World!");
}
return 0;
}
来看看打印的结果:

method1
:获取类MlqqObject
的实例方法bitsTest
,有值method2
:获取类MlqqObject
的元类
的实例方法bitsTest
,无值method3
:获取类MlqqObject
的实例方法hobbyTest
,无值method4
:获取类MlqqObject
的元类
的实例方法hobbyTest
,根据上面的分析知道,类方法是存在元类里面的,所以有值
2.6.2 类方法
再获取类方法,在main
函数里面调方法2:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
MlqqObject * objc = [[MlqqObject alloc] init];
Class mClass = object_getClass(objc);
classMethod_classToMetaclass(mClass);
NSLog(@"Hello, World!");
}
return 0;
}
看看结果:

method5
:获取类MlqqObject
的类方法bitsTest
,无值method6
:获取类MlqqObject
的元类
的类方法bitsTest
,无值method7
:获取类MlqqObject
的类方法hobbyTest
,有值method8
:获取类MlqqObject
的元类
的类方法hobbyTest
,也有值
那么现在问题来了,为什么MlqqObject
的元类
中既有实例方法hobbyTest
又有类方法hobbyTest
呢,我们接着往下分析
2.6.3 class_getClassMethod分析
我们来看看class_getClassMethod
的源码:
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
从上面的源码我们可以得出结论:
获取类方法的本质就是获取元类的实例方法
再来看看getMeta()
的源码:
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
也就是说只要
cls
是元类,cls->getMeta()
得到的就是cls
本身,并不会再去找cls
的元类了,看到这里我们在来看method4
和method8
,metaClass
都是MlqqObject
的元类,所以class_getClassMethod(metaClass, @selector(hobbyTest))
等价于class_getInstanceMethod(metaClass, @selector(hobbyTest))
,这就是MlqqObject
的元类
中既有实例方法hobbyTest
又有类方法hobbyTest
的原因
这里简单介绍一下lldb
里面参数打印的规则:
1、这里都是用的p
命令,因为底层都没有描述方法,所以不能用po
2、指针类型用->
方式调用方法,例如:$1->data()
3、结构体类型用.
方式调用方法,例如:$3.properties()
4、指针类型,可以用*
的方式取值,例如:*$2
5、$1
、$2
、$3
、$4
等都是临时变量,方便调用
网友评论