之前的OC类的探索-isa 一文中主要说了下类中的isa的指向关系,本文主要关注class_data_bits_t

准备

数据准备

我们事先创建一个名为FMUserInf苹果o的类,内有属性成员变量类方法实例方法,如elementary是什么意思下所示

@interface FMUserInfo : NSObject {
    NSString *_hobby;
}
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end

内存平移

当我们定义一个整函数调用栈形数苹果官网组,通常的获取数据地址的方式为下标获取,如下代码所示:

// 数组指针
int c[4] = {5,6,7,9};
NSLog(@"%p - %p - %p - %p - %p",&c,&c[0],&c[1],&c[2],&c[3]);

当然也可以通过内存平移的方式来获取到数组中的每个地址

NSLog(@"%p - %p - %p - %p",d,d+1,d+2,d+3);

其结果如下

OC类的探索-bits
这里我们看到 当我们打印d+1的时候,他的内存地址就跟c的第二个元素的内存地址相同,这就是内存平移,这里平移的大小就是4,因为int是4字节

同样,由于结elementanimation构体的内存也是连续的,我们也可以通过内存平移的方式来读取到结构体里的值。

如下代码,我们定义elementary两个结构体FMMemo函数调用栈r电脑黑屏却开着机y,class_data_bits_t,在结构elements中文翻译FMMemorydoubleC函数调用lass均为8字节,理论上通过结构体的首地苹果x址平移16字节就可以得到bits

struct class_data_bits_t {
    double c;
};
struct FMMemory {
    double a;
    Class d;
    struct class_data_bits_t bits;
} m;

那我们就可以尝试下:

OC类的探索-bits
事实证明通过苹果内存平移的方式可苹果官网以获取到结构体里边参数的值,通过这种方式,我们也可以用于探索objc_class结构体中的参数。

class_data_bits_t 获取

函数调用栈们知道类对象苹果的底层是一个名为objc_class的结构体,如下代码所示:

//类对象
struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass;// 8
    cache_t cache;   // 16         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags //
    ......
}

之前的文章中也有提及,类中的数据被存储函数调用栈class_data_bit电脑怎么录屏s_t的结构体中,通过内存平移的方式我们可以获取到clas函数调用的四个步骤s_data_bits_t

OC类的探索-bits

一、class_rw_t

cl苹果ass_data_bits_t中,我们看到苹果提供了获取data数据的方法class_rw_t* data()

OC类的探索-bits

OC类的探索-bits

  • p $3.data()调用data方法,返回class_苹果13rw_t类型的结构体。

class_rw_t存储了当苹果官网前类在编译期就已经确定的属性方法以及遵循的协议,里面是没有分类的方法的。(那elements中文翻译些运行时添加的方法函数调用的三种方式会存储在class_rw_ext_t中,下文会提及)。

1、方法列表 methods

通过查看class_rw_t源码可以得知,电脑开不了机我们可以通过methods()properties()protocols()方法来获取类中存储的方法属性协议

OC类的探索-bits

我们可以通过如下element翻译方式来打印类中存储的数据;

OC类的探索-bits

  • p $4->methods()class_rw_t结构体所提供的方法,返回方法列表。函数调用可以作为独立的语句存在
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};
        }
    }

在这里我们可以看到一个比较有意思的地方,methods方法先获取ro_or_rwe,然后内部有一个判断,用来判断是取class_rw电脑_ext_t里边的methods还是classelementanimation_ro_t中的baseMethods

(1) ro、rw、rwe解析

那么这里的class电脑怎么重装系统_rw_ext_t是什么呢?我们可以先看下苹果的官方苹果官网解释:官网链接地址

通过视频介绍我们要首先知道的两个概念:

  • clean memory是指加载后不会发生更改的内存class_ro_t就属于clean memory是只读的。
  • dirty memory是指在程序运行时是会发生更改的内存

注意:
类一旦被使用就会变成dirty memory,因为runtime会向它写入新的数据,d函数调用可以出现在表达式中吗irty memoryclean memory更加消耗内存和性能, 只要程序在运行, 它就必须一直存在.
clean memory可以进行移除, 从电脑而节省更多的内存空间. 如果你需要cleanelementui mem函数调用可以作为独立的语句存在ory系统可以从磁盘中重新加载;

class_ro_t

class_relement翻译o_t是在编译的时候⽣成的。当类在编译的时候,类的属性,函数调用可以作为独立的语句存在实例⽅法,协议这些内容就存在class_ro_t这个结构体⾥⾯了,这是⼀块纯净的内存空间,不允许被修改。

class_rw_t

class_rw_t是在运⾏的时候⽣成的,类⼀经使⽤函数调用栈就会变成class_rw_t,它会先将class_r函数调用o_t的内容”拿”过去,然后再将当前类的分类elementary是什么意思的这些属性、⽅法等拷⻉到class_rw_t⾥⾯。它是可读写的。

class_rw_ext_t

class_rw_ext_t可以减少内存的消耗。苹果在wwdc电脑开不了机2020⾥⾯说过,只有⼤约10%左右的类需要动态修改。所以只有10%左电脑开不了机右的类⾥⾯需要苹果官网⽣成class_rw_ext_t这个结构体。这样的话,可以节约很⼤⼀部分内存。

(2) class_rw_ex函数调用栈t_t⽣成的条件:

  • 第⼀:⽤过runtime的Api进⾏动态修改的时候。
  • 第⼆:有分类的时候,且分类和本类都为⾮懒加载类的时候。实现了+load⽅法即为⾮懒加载类。

(3) 类的结构变化

当类第一次从磁盘加载到内存苹果7时的结构。

OC类的探索-bits
当需要修改类信息的时候
OC类的探索-bits
当遇到runtime需要对类动态修改或者有分类的时候,将需要动态更新的部分提取出来,存入class_rw_ext_t苹果官网

OC类的探索-bits
类整体结构
OC类的探索-bits


回归前文

  • p $8.get(0).getSmallDescriptielementanimationon(): 我们看到$8是m电脑怎么重装系统ethod_list_t结构,它是继承entsize_list_tt

(4)entsize_list_tt

entsize_list_tt是一个模板结构体,其定义如下:

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
	……
 Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
        ……
}

entsize_list_tt可以看作一个容器,Element可以看作范型TList为容器类型,容器中提供get方法来返回容器中的对象,可以通过get(0)、get(1)elementui...来迭代返回容器中的内容。


  • getSmallDescription获取小端模式method_t的数据.

(5)big & sm函数调用的四个步骤all 大端小端

大端模式函数调用的三种方式:是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中(网络上一般都是采用大端模式)
小端模式,是指数据的高字节保存在电脑怎么重装系统内存的高地址中,而数据的低字节保存在内存的低地址elementary翻译

例如某个值为0x12345678如何保存在内存中,内存地址是连续的,如需存在起始函数调用可以出现在表达式中吗地址为0x10001的内存地址中,

0x12345678  0x10001  0x10002  0x10003  0x10004
      大端:    0x12     0x34     0x56     0x78 
      小端:    0x78     0x56     0x34     0x12

由于我的电脑是M1的电脑,所以我们可以调用small()方法来查看小端类型的数据。

OC类的探索-bits

但是通过打印发现,small()方法打印出来的数据都是地址的相对偏移,这也是其定义导致的。

 ......
 struct big {
     SEL name;
     const char *types;
     MethodListIMP imp;
 };
 ......
 struct small {
        // The name field either refers to a selector (in the shared
        // cache) or a selref (everywhere else).
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP, /*isNullable*/false> imp;
        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));
        }
 };
 ......
 objc_method_description *getSmallDescription() const;

通过查看源码发苹果12现,大端类型big保存的就是具体的数据,苹果因不送充电器被判赔7000元但是small中存的是RelativePointer相对地址偏移,幸运的是苹果也提供了获取small数据的方法getSmallDes苹果官网cription(),我们可以通过p $8.get(0).ge函数调用的方法tSmallDescription()电脑安全模式来获取method_t的数据。

我们苹果因不送充电器被判赔7000元在前文中说到method_list_t中存储着类中的方法电脑锁屏快捷键列表,通过打印发现FMUserInfo中一共有6个实例elementanimation方法。

OC类的探索-bits

OC类的探索-bits
这里发现除了自定义的instanceMethod方法和属性name、age生成的set、get外,还有一个cxx_destruct方法。

注意:cxx_destruct方法

  • 只有在ARC下这个方法才会出现函数调用中的参数太少,用于释放成员变量。
  • 只有当前类拥有成员变量时(不论是不是用property)这个方法才会出现函数调用中的参数太少,且父类的实例变量不会导致子类拥有这个方法
  • 出现这个方法和变量是否被赋值,赋值成什么没有关系。

2、属性列表 properties

我们按照获取methods方法的方式,再来获取一下属性列表prope函数调用可以作为独立的语句存在rties,如下图所示:

OC类的探索-bits

prope苹果官网rty_t

我们可以看到prop苹果13erties()返回的数据最终的类型为property_t类型,其结构为:

struct property_t {
    const char *name;
    const char *attributes;
};

所以我们可以直接通过get(0)、get(1)直接将数据打印出来。


3、c电脑截图快捷键lass_ro_t

claelementanimationss_rw电脑怎么录屏_t结构体中,我们可以看到有一个返回为class_ro_t的结构体,成员变量存放在class_ro_t结构体当中;

const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

接下来我们看下class_ro_t的结构:

OC类的探索-bits

OC类的探索-bits

OC类的探索-bits
查看源码发现class电脑截图_ro_t中存在ivar函数调用中的参数太少_list_t的结构体,该结构体同样继承自entsize_list_tt说明ivar_list_t也是个容器,也可以通过get(0),get(1)来获取,且容器中的内容为iv苹果12ar_t,其中就定义有n消息机制ame,type,接下来我们就具体打印下。

OC类的探索-bits

OC类的探索-bits

属性与成员变量

这里要注意属性与成员变量的区别,在我们苹果官网当时定义FMUserInfo的时候:

@interface FMUserInfo : NSObject {
    NSString *_hobby;
}
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end

成员变量:
这段苹果手机代码中 中括号函数调用是什么意思{}里的_hobby就为成员变量,函数调用是什么意思成员变量只用于类的内部,外界无法访问。
电脑怎么录屏类中存储在ivar_list_t结构体中,通过苹果7ro函数调用是什么意思()方法获取。

属性
这段代码中,name age就是属性,其用@property声明,属性在编译过程中会自动生成set 和get方法。属性的好处就是允许让其苹果因不送充电器被判赔7000元他对象访电脑快捷键问到该变量。
函数调用的三种方式类中存储在prop苹果8erty_list_t结构体中。通过properties()方法获取。其生成的set、get方法苹果12通过methods()方法获取

注意:为什成员变量存放在类对象中,而成员变量的值存放在实例对象中,因为实例对象有很多,不电脑快捷键同的实例对象存储的值不苹果8同;

二、类方法的存储

下面我们看elementui下类方法的存储,类方法消息机制存储在元类之中。

  • 首先我们获取元类FMUserInfo

OC类的探索-bits

  • 然后获取class_data_bits_t

OC类的探索-bits

  • 然后通过data()获取class_rw_t,通过methods()方法,获取到me电脑黑屏却开着机th函数调用语句od_list_t

OC类的探索-bits

  • 接下来就是打印method_电脑怎么重装系统t,我们看到method_list_t中只有一条数据,通过get(苹果130)就可以获电脑开不了机取到。

OC类的探索-bits

三、为什么要设计元类

  • 首先从设计上来说,类也是一个对函数调用是什么意思象,在描述类的时候也需要一种类来描述类对象,元类的出现是为了解决描函数调用的方法述类对elementary翻译的问题。

  • 众多编程语言的元类定义也各不相同,python中的类默认都是同一个MetaClass(Python里可以自定义新的MetaClass),而OC中每个类都有一个MetaClass,这么做的目的主要是为了苹果手机消息机制

  • 在OC中调⽤⽅法,其elementui实是在给某个对象发电脑安全模式送某条消息。消息的发送在编译的时候编译电脑截图快捷键器就会把⽅法转换为objc苹果手机怎么录屏_msgSend这个函数。id ob函数调用可以出现在表达式中吗jc_msgSend(id selElementf, S消息机制EL op, ...) 这个函数有俩个隐式的参数:消息的接收者,消息的⽅法名。通过这俩个参数就能去找到对应⽅法的实现。

  • 在OC中实例方法和类方法是可以同名的,之所以可以同名,是因为在调用方法的时候通过objc_msgSend传递的第一个参数不同,可以调用到不同的方法实现。如果是实例方法苹果7,通过解析isa指针就会找到实例对象对应的类对象,然后调elementary翻译用实例方法;如果是类方法,就会通过isa找到类对象的elementanimation元类,然后调用类方法。

  • 那如果没有苹果8元类的话,那这个objc_msgSend⽅法还得多加俩个参数,⼀个参数⽤来判断这个⽅法到底是类⽅法还是实例⽅法。⼀个参数⽤来判断消息的接受者到底是类对象还是实例对象。消息的发送,越快越好。那如果没有元类,在objc_msgSend内部就会有很多判断,影响消息的发送效率。

  • 元类的出现电脑怎么录屏就解决了这个问题,让苹果7各类各司其职,实例对象就⼲存储属性值的事,类对象存储实例⽅法列表,元类对象存储类⽅法列表,符合设计原则中的单⼀职责;由于复⽤消息传递这套机制,不同种类的函数调用可以作为独立的语句存在⽅法⾛的都是苹果x同⼀套流程,在之后的维护上也⼤⼤节约了成本。

苹果范冰冰于为什么elementanimation不同种类的⽅法⾛的都是同⼀套流程,除了前文中提及的objc_ms电脑怎么录屏gSend外,我们可以先看一段代码:

@interface FMUserInfo : NSObject {
- (void)instanceMethod;
+ (void)classMethod;
@end
-------------------
-(void)methodTest:(Class)pClass {
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    //instanceMethod为实例方法 //classMethod为类方法
    Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethod));
    Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
    Method method3 = class_getInstanceMethod(pClass, @selector(classMethod));
    Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));
    Method method5 = class_getClassMethod(pClass, @selector(classMethod));
    NSLog(@"%p - %p - %p - %p - %p",method1,method2,method3,method4,method5);
}
--------调用----------
 [self methodTest:FMUserInfo.class];

然后我们看下打印结果:

OC类的探索-bits

method4是从元类中获取实例方法,methoelement滑板d5是从类pClass中获取类方法,但这两者的结果是一致的。
那我们看下class_getClassMethod的源码:苹果因不送充电器被判赔7000元


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

通过源码我们可以知道,class_getClassMethod其本质就是调用c苹果官网lass_getInstanceMethod,在OC底层没有类⽅法和elementary实例⽅法的区别,都是函数调用

四、函数调用可以出现在表达式中吗通过runtime获取类的数据

获取类的成员变量

//获取类的成员变量
-(void)fm_class_copyIvarList:(Class)pClass {
    unsigned int  outCount = 0;
    Ivar *ivars = class_copyIvarList(pClass, &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        const char *cName =  ivar_getName(ivar);
        const char *cType = ivar_getTypeEncoding(ivar);
        NSLog(@"name = %s type = %s",cName,cType);
    }
    free(ivars);
}

其结果为:

OC类的探索-bits
我们看下class_copyIvarList源码:

Ivar *
class_copyIvarList(Class cls, unsigned int *outCount)
{
    const ivar_list_t *ivars;
    Ivar *result = nil;
    unsigned int count = 0;
    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }
    mutex_locker_t lock(runtimeLock);
    ASSERT(cls->isRealized());
    if ((ivars = cls->data()->ro()->ivars)  &&  ivars->count) {
        result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
        for (auto& ivar : *ivars) {
            if (!ivar.offset) continue;  // anonymous bitfield
            result[count++] = &ivar;
        }
        result[count] = nil;
    }
    if (outCount) *outCount = count;
    return result;
}

我们发现其获element翻译取的路径苹果7clelement翻译s->data()->ro()->ivars,与前文中我们分析的路径一致。

获取属性

//获取类的属性
-(void)fm_class_copyPropertyList:(Class)pClass {
    unsigned int outCount = 0;
    objc_property_t *perperties = class_copyPropertyList(pClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = perperties[i];
        const char *cName = property_getName(property);
        const char *cType = property_getAttributes(property);
        NSLog(@"name = %s type = %s",cName,cType);
    }
    free(perperties);
}

其结果为:

OC类的探索-bits
我们看下class_copyPropertyList源码:

objc_property_t *
class_copyPropertyList(Class cls, unsigned int *outCount)
{
    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }
    mutex_locker_t lock(runtimeLock);
    checkIsKnownClass(cls);
    ASSERT(cls->isRealized());
    auto rw = cls->data();
    property_t **result = nil;
    auto const properties = rw->properties();
    unsigned int count = properties.count();
    if (count > 0) {
        result = (property_t **)malloc((count + 1) * sizeof(property_t *));
        count = 0;
        for (auto& prop : properties) {
            result[count++] = &prop;
        }
        result[count] = nil;
    }
    if (outCount) *outCount = count;
    return (objc_property_t *)result;
}

通过源苹果码,我们发现prop函数调用可以作为一个函数的形参erties的获取是通过properties = rw->properties();,其最终调用的是cls->data()->pro苹果12perties()

获取实例方法

//获取类的实例方法
-(void)fm_class_copyMethodList:(Class)pClass {
    unsigned int outCount = 0;
    Method *methods = class_copyMethodList(pClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        Method method = methods[i];
        NSString *name = NSStringFromSelector(method_getName(method));
        const char *cType = method_getTypeEncoding(method);
        NSLog(@"name = %@ type = %s",name,cType);
    }
    free(methods);
}

其结果如下:

OC类的探索-bits
其源码为:

Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{
    unsigned int count = 0;
    Method *result = nil;
    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }
    mutex_locker_t lock(runtimeLock);
    const auto methods = cls->data()->methods();
    ASSERT(cls->isRealized());
    count = methods.count();
    if (count > 0) {
        auto iterator = methods.signedBegin();
        auto end = methods.signedEnd();
        result = (Method *)malloc((count + 1) * sizeof(Method));
        count = 0;
        for (; iterator != end; ++iterator) {
            result[count++] = _method_sign(&*iterator);
        }
        result[count] = nil;
    }
    if (outCount) *outCount = count;
    return result;
}

其最终调用的是cls->data()->methods()