美文网首页
五、类原理之bits

五、类原理之bits

作者: Mlqq | 来源:发表于2021-03-30 07:46 被阅读0次

1、指针平移

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

image.png
这张图的意思呢就是定义一个整型数组intArray,里面的元素分别为102648,可以利用intArray[0]方式取到元素10,其他的元素依次类推,这个大家都知道。还可以采用下面的方式:
image.png
简单解释一下:int * p = intArray就是定义一个指针p指向数组intArray,我们知道数组的地址就是首元素的地址,所以*p取得值就是第一个元素,p+1就是指针往后移动一位也就是指向第二个元素,*(p+1)取得就是第二个元素,依次可以取到数组中所有的元素,这就是指针平移的用法。

2、class内存结构

class的指针平移跟上面讲到的平移一样,想要平移指针首先要知道class每个属性占的字节数,然后才能平移到我们想要的位置,我们先来回顾一下class的结构(818版本):

image.png
从定义可以看到class里面总共有4个属性,分别是isasuperclasscachebits
  • 获取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版本):

image.png
这个定义里面有很多static类型的属性,static类型属性是不存在结构体里面的,所以我们只需关注红色部分就可以了。
其实就是两部分:_bucketsAndMaybeMaskunion
(1)_bucketsAndMaybeMaskuintptr_t类型,看一下定义: image.png
就是无符号长整型,8字节。
(2)union通过前面的介绍,里面的struct_originalPreoptCache是共用内存的,我们只看一个就可以了,最简单的是_originalPreoptCache,就是一个preopt_cache_t指针,所以也是8字节。

总结:通过分析我们知道了cache的大小就是16字节。

2.2 bits分析

class里面属性我们已经知道isasuperclass,还剩cachebitscache看名字就知道是缓存用的,所以接下来我们就先研究一下bits
x/8gx查看一下MlqqObject内存结构:

image.png
在图中可以轻松找到bits的起始地址,我们在验证一下对不对,按照图中方法转化一下:
image.png
能正常打印出来是class_data_bits_t类型指针,说明我们移位没问题。

我们只拿一个指针没什么用,关键是要看指针里面指向的内容是什么,在objc_class的可以找到下面的方法:

class_rw_t *data() const {
    return bits.data();
}

可以看到bits有一个data()方法,我们也可以调用,看看里面返回什么内容:

image.png
确实是返回了一个class_rw_t类型的指针,接着看一下这个指针的内容是什么
image.png
看到这里还是没能看到我们想看的内容,既然是类的结构,那总要看到跟类相关的方法、属性等内容吧。我们打印的$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定义的方法和属性:


image.png

2.3.1 获取methods

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

image.png

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

image.png

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

image.png

打印一下$6的值:

image.png

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

image.png

取出来变量$8没有信息,但是可以看到是一个method_t类型,搜一下源码可以看到method_t定义中有很多方法,我们这里打印一下其描述信息看一下,先看一下方法:

objc_method_description *getDescription() const {
        return isSmall() ? getSmallDescription() : (struct objc_method_description *)this;
    }
image.png

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

image.png

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

image.png
这样可以将类里面的方法全部遍历出来了。但是我们没有看到+ (void)hobbyTest这个类方法。
超过数组数量就会发生越界。

2.3.2 获取properties

我们在来看看获取properties方式,前面跟获取methods一样,只是在将methods()方法换成properties()方法,其他的获取方式都是一样的:

image.png
这样我们就可以看到所有的属性了,但是我们没有看到hobby这个成员变量。

2.3.2 获取protocols

这个跟上面两个获取的方式一样,大家可以自己动手试试。

总结:这篇要介绍的已经介绍完了,还有两个问题:
1、+ (void)hobbyTest这个静态方法我们没看到存在哪里
2、hobby这个成员变量我们也没看到存在哪里

2.4 获取成员变量

其实成员变量是存在结构体class_rw_tro里面,获取方式如图:

image.png
获取结构体class_ro_tivars
image.png
image.png
这样就可以取到类的成员变量了

2.5 获取类方法

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

image.png
接下来就是获取methods,参考2.3.1就不一步步展示了,直接看结果:
image.png

这里验证了类方法是存在元类里面的

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

来看看打印的结果:

image.png
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;
}

看看结果:

image.png
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的元类了,看到这里我们在来看method4method8metaClass都是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等都是临时变量,方便调用

相关文章

网友评论

      本文标题:五、类原理之bits

      本文链接:https://www.haomeiwen.com/subject/tdygcltx.html