isa 走位图

在讲 OC->Class 底层类结构之前,先看下下面这张图:

通过isa走位图 得出的结论是:

1,类,父类,元类都包含了 isa, superclass

2,对象isa指向类对象,类对象的isa指向了元类,元类的 isa 指向了根元类,根元类 isa 指向自己

3,类的 superclass 指向父类,父类的 superclass 指向的根类,根类的superclass 指向的nil

4,元类的 superclass 指向父元类,父元类 superclass 指向的根元类,根元类 superclass 指向根类,根类 superclass 指向nil

这下又复习了 isasuperclass 走位;那么问题这些类,类对象,元类对象当中的在底层展现的数据结构是怎样呢,这是我需要探索的,于是把源码贴出来展开分析下:

struct objc_class

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
class_rw_t *data() const {
return bits.data();
}
const class_ro_t *safe_ro() const {
return bits.safe_ro();
}
}

从源码没见 isa 属性,其实它继承了objc_object ,而 objc_object 中有个isa ,在运行时类图生成中会产生一个isa 指向objc_object 这个类图,而 superclass 指向它的父类;根据上面 isa , superclass 走位图就知道它的指向关系。

cache_t & class_data_bits_t

cache 方法缓存,这个作用将常调用的方法缓存下来;便于下次直接查找调用,提高查找效率。

它的结构:

struct cache_t {
struct bucket_t *buckets() const;//存储方法的散列表
mask_t mask() const;//散列表缓存长度
mask_t occupied() const;//已缓存方法个数
}
struct class_data_bits_t {
class_rw_t* data() const;//类信息
}

bits 存储具体类信息,它需要&FAST_DATA_MASK来计算得到类心所有信息,源码如下:

FAST_DATA_MASK 掩码值

bool has_rw_pointer() const {
#if FAST_IS_RW_POINTER
return (bool)(bits & FAST_IS_RW_POINTER);
#else
class_rw_t *maybe_rw = (class_rw_t *)(bits & FAST_DATA_MASK);
return maybe_rw && (bool)(maybe_rw->flags & RW_REALIZED);
#endif
}

通过源码确实需要这种方式计算能得到类的存储信息;那为什么要用这种方式去处理呢。

比如说我要得到存储在 class_rw_t 类信息信息我只要通过 FAST_DATA_MASK 掩码值就能得到它的地址信息,通过地址信息就能从内存中拿到所有类的存储信息。

那这样我的FAST_DATA_MASK掩码值不一样,我通过&计算,得到的数据信息也就不一样,不得不说苹果工程师想的周到,而且这种方式不仅isa也是这样,很多地方都用这种方式取值,大大提高访问速度,数据提取效率。

class_rw_t ,class_ro_t,class_rw_ext_t

struct class_rw_t {
const class_ro_t *ro() const ;
const method_array_t methods() const ;//如果是类对象:放对象方法,元类:元类对象方法 const property_array_t properties() const;
const protocol_array_t protocols() const;
class_rw_ext_t *ext() const;
}
struct class_rw_ext_t {
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
uint32_t version;
}

可以看出类的信息具体就存储在class_rw_tclass_ro_tclass_rw_ext_t 中,

剖析下class_rw_t

先看看method_array_tproperty_array_tprotocol_array_t源码结构

class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super; public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
}; class protocol_array_t :
public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
{
typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super; public:
protocol_array_t() : Super() { }
protocol_array_t(protocol_list_t *l) : Super(l) { }
};

看完之后,他们都继承list_array_tt,那么 list_array_tt 是什么鬼,它数据结构是怎样的,这下在取找下它。源码如下:

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
protected:
template <bool authenticated>
class iteratorImpl {
const Ptr<List> *lists;
const Ptr<List> *listsEnd;
} using iterator = iteratorImpl<false>;
using signedIterator = iteratorImpl<true>; public:
list_array_tt() : list(nullptr) { }
list_array_tt(List *l) : list(l) { }
list_array_tt(const list_array_tt &other) {
*this = other;
} void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return; if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray =(array_t*)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount; for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
} }

我把主要地方拿去出来,可以看到 attachLists 它的目的是将一个或多个列表(List 类型)附加到某个 list_array_tt对象中。这个对象可以包含零个、一个或多个列表,这些列表可以是单个指针,也可以是指针数组。函数的输入参数是一个指向 List 指针数组的指针 addedLists 和一个无符号整数 addedCount,表示要添加的列表数量。

由此我推断它是一个数组,而且是一个二维数组存储的,所有由此得出 class_rw_tmethodspropertiesprotocols这几个属性利用二维数组取存储类的方法,协议等信息,而且是可读可写的属性。

那它设计这种二维数组有什么好处呢?当然有好处,它可以动态的给数组里面增加删除方法,很方便我们分类方法的编写完进行存储。

那搞清楚了 class_rw_t 几个重要数据存储信息,那 class_rw_t 它的作用是干什么的呢;

class_rw_t 结构体定义来看;它是在应用运行时,将OC类,分类的信息直接写入到class_rw_t结构的数据结构中,在类的方法,协议进行调用时,从里面去读取,然后常调用的方法,又存储在cache_t这个结构体中,可想而知,苹果对OC类的处理,煞费苦心。

struct class_ro_t

class_rw_t结构体中有个 class_ro_t 结构体,在探索下这个东西做什么的,它的源码如下:

struct class_ro_t {
WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
property_list_t *baseProperties;
}

先说说 ivars 这个属性修饰的结构体源码如下:

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};

这个貌似只有一个继承 entsize_list_tt,那在探索下源码:

struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
struct iteratorImpl {
uint32_t entsize;
uint32_t index; // keeping track of this saves a divide in operator- using ElementPtr = std::conditional_t<authenticated, Element * __ptrauth(ptrauth_key_process_dependent_data, 1, 0xdead), Element *>; ElementPtr element; typedef std::random_access_iterator_tag iterator_category;
typedef Element value_type;
typedef ptrdiff_t difference_type;
typedef Element* pointer;
typedef Element& reference; iteratorImpl() { } iteratorImpl(const List& list, uint32_t start = 0)
: entsize(list.entsize())
, index(start)
, element(&list.getOrEnd(start))
{ }
}
}

可以看出这段代码定义了一个结构体 entsize_list_tt,它内部包含一个嵌套的结构体 iteratorImpl,用于实现一个迭代器。遍历容器(如列表、数组等)的对象。

到此可以得出ivars 是一个 ivar_list_t 数组,它存储了类的属性变量信息,那protocol_list_t结构体内部也是数组形式构建的。

baseProtocolsbaseProperties 这两个属性对类的存储信息只能读取,不能写入。

所以总结的是:从 class_ro_t 结构体定义来看,它存储类的变量,方法,协议信息,而且这个结构体属于类的只读信息,它包含了类的初始信息。

class_rw_ext_t

这个结构体不在过多叙述,简单来说它是基于 class_rw_t 之后为了更好管理oc类的高级特性,比如关联属性等,衍生出来的一个结构体,包括:method_array_t ,property_arrat_t ,protocol_array_t 等定义属性类型

到这里类结构及存储所关联的信息都在这里了;来一张他们关联的结构思维图:

总结:一开始编译时,程序将类的初始信息放在 class_ro_t中,当程序运行时,将类的信息合并在一起的时候,它会将 class_ro_t 类的信息合并到 class_rw_t 结构体中去。

struct method_t

为什么要说method_t,因为它不仅在 class_ro_t 有使用,在OC底层其他地方也有使用;比如如下源码:

void method_exchangeImplementations(Method m1Signed, Method m2Signed)
{
if (!m1Signed || !m2Signed) return; method_t *m1 = _method_auth(m1Signed);
method_t *m2 = _method_auth(m2Signed); mutex_locker_t lock(runtimeLock); IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name(); m1->setImp(imp2);
m2->setImp(imp1); // RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally? flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
}); adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
} static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
lockdebug::assert_locked(&runtimeLock); if (!m) return nil;
if (!imp) return nil; IMP old = m->imp(false);
SEL sel = m->name(); m->setImp(imp); // Cache updates are slow if cls is nil (i.e. unknown)
// RR/AWZ updates are slow if cls is nil (i.e. unknown)
// fixme build list of classes whose Methods are known externally? flushCaches(cls, __func__, [sel, old](Class c){
return c->cache.shouldFlush(sel, old);
}); adjustCustomFlagsForMethodChange(cls, m); return old;
}

方法交换,实现中底层都有用到,我们探索下,先看看 method_t 源码:

struct method_t {

    // 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;
}; // A "big" method, but name is signed. Used for method lists created at runtime.
struct bigSigned {
SEL __ptrauth_objc_sel name;
const char * ptrauth_method_list_types types;
MethodListIMP imp;
}; // ***HACK: This is a TEMPORARY HACK FOR EXCLAVEKIT. It MUST go away.
// rdar://96885136 (Disallow insecure un-signed big method lists for ExclaveKit)
#if TARGET_OS_EXCLAVEKIT
struct bigStripped {
SEL name;
const char *types;
MethodListIMP imp;
};
#endif }

可以看到这结构体中掐套了多个结构体;在把它简化下:

struct method_t {
SEL name;//方法名
const char *types;//包含函数具有参数编码的字符串类型的返回值
MethodListIMP imp;//函数指针(指向函数地址的指针)
}

SEL :函数名,没特别的意义;

特点:

1,使用@selector()sel_registerName()获得

2,使用sel_getName()NSStringFromSelector()转成字符串

3,不同类中相同名字方法,对应的方法选择器是相同或相等的

底层代码结构:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

types:包含了函数返回值、参数编码的字符串



可以看到types在值:v16@0:8 ,可以看出name,types,IMP其实都在class_ro_t结构体中,这样确实证明了之前说的;class_ro_t结构体在运行时存储着类的初始状态数据。

v16@0:8说明下:

v:方法返回类型,这里说void,

16:第一个参数,

@:id类型第二个参数,

0:第三个参数

: :selector类型

8:第四个参数

那这种types参数又是什么鬼东西,查下了资料这叫:Type Encoding(类型编码)

怎么证明了,使用如下代码:

苹果官网types encoding表格:

IMP 其实就是指向函数的指针,感觉这个就没有必要讲了。

struct cache_t

cache_t 用于 class的方法缓存,对class常调用的方法缓存下来,提高查询效率,这个上之前都已经说过;接下来看看 bucket_t

struct bucket_t

struct bucket_t {
cache_key_t _key;//函数名
IMP _imp;//函数内存地址
}

这种散列表的模型,其实在底层用一个数组展现:

其实它的内部就是一个一维数组,那可能问了,数组难道它是循环查找吗,其实不然;在它元素超找时,它是拿到你的 函数名 & mask,而这个 mask 就是 cache_t 结构体中的 mask值;计算得到函数在 散列表 存储的索引值,在通过索引拿到函数地址,进行执行。

接下来看个事例:

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        Student *stu=[Student new];

        [stu test];

        [stu test];

        [stu test];

        [stu test];

    }

    return 0;

}

如上方法:当首次调用它会去类对象中查找,在方法执行时,他会放入cache_t 缓存中,当第二次,第三次,第四次时,它就去缓存中查找。

当方法执行后;我们看到 _mask 是:3,这个3代表了我类中定义了三个函数;而——_occupied 是一个随意的值;它其实代表了换存方法的个数。

那如何知道方法有缓存了,再继续往下执行:

这时候执行完 test02, _mask的值从 3 变成了 7 ,说明散列表 bucket_t 做了扩容操作。在这里bucket_t 元素需要 _mask 个元素,所以最终 bucket_t 从原有的3个元素进行了 2倍 扩容。

在看下方法是否进行缓存:

可以看见当执行完 [stu test02] 时,数据做了扩容,并且扩容的数据使用(null) 进行填充。

在看个事例:

在执行 [stu test] 之前;其实bucket_t 就3个元素,并且存入了 init 方法;

当执行完 [stu test] 之后;就存入 test 方法。

但是注意的地方:它在扩容时对之前的缓存进行清除。

通过查看源码,我们知道了它如何进行清除操作,

当执行完 [stu test02];[stu test03]; 之后,它先将缓存清空;这时候 init , test 方法被清空,bucket_t扩容完在存储:test02test03 方法。

那问题又来了,它是如何快速定位到方法的,然后执行的?接下来看看代码:

可以清楚看见,当我使用 @selector(test03)&stu_cache._mask 就可以得到下标,然后再从 bucket_t 拿到方法。

到这里 class结构,类的方法缓存到此结束了,从上面也可以思考下:如果自己去实现散列表数组,是不是思路就跟清晰了。

谢谢大家!青山不改,绿水长流。后会有期!

objective-c之Class底层结构探索的更多相关文章

  1. React Native APP结构探索

    APP结构探索 我在Github上找到了一个有登陆界面,能从网上获取新闻信息的开源APP,想来研究一下APP的结构. 附上原网址:我的第一个React Native App 具体来讲,就是研究一个复杂 ...

  2. V7000存储数据恢复_底层结构原理拆解及Mdisk磁盘掉线数据恢复方法

    Storwize V7000(也就是我们常说的V7000)是新推出的一款中端存储系统,这款系统的定位虽然在中端,但是Storwize V7000提供有存储管理功能,这一功能以前只有高端存储才拥有(例如 ...

  3. [转]谈NAND Flash的底层结构和解析

    这里我想以一个纯玩家的角度来谈谈关于NAND Flash的底层结构和解析,可能会有错误的地方,如果有这方面专家强烈欢迎指正. NAND Flash作为一种比较实用的固态硬盘存储介质,有自己的一些物理特 ...

  4. LWIP的底层结构(物理层)

    LWIP的底层结构(物理层) 转自:http://bluefish.blog.51cto.com/214870/158418 我们前面讲到说是ip层的发送和接收都是直接调用了底层,也就是设备驱动层的函 ...

  5. HP-lefthand底层结构具体解释及存储灾难数据恢复

    HP-lefthand底层结构具体解释及存储灾难数据恢复 一.HP-lefthand的特点 HP-lefhand是一款很不错的SAN存储,使用iscsi协议为client分配空间. 它支持RAID5. ...

  6. Redis底层结构全了解

    第一篇文章,思来想去,写一写Redis吧,最近在深入研究它. 一丶Redis底层结构 1. redis 存储结构 redis的存储结构从外层往内层依次是redisDb.dict.dictht.dict ...

  7. String,StringBuffer,StringBuillder的底层结构

    一:StringBuffer的底层 (1)线程安全的字符串操作类 (2)通过synchronized关键字声明同步方法,保证多线程环境下数据安全 public synchronized StringB ...

  8. java集合专题 (ArrayList、HashSet等集合底层结构及扩容机制、HashMap源码)

    一.数组与集合比较 数组: 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加/删除元素-比较麻烦 集合: 1)可以动态保存任意多个对象,使用比较方便 ...

  9. Java程序猿从笨鸟到菜鸟之(九十二)深入java虚拟机(一)——java虚拟机底层结构具体解释

    本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在曾经的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本的语法 ...

  10. HashMap底层结构、原理、扩容机制

    https://www.jianshu.com/p/c1b616ff1130 http://youzhixueyuan.com/the-underlying-structure-and-princip ...

随机推荐

  1. JOISC 2021 记录

    Day1 T1 Aerobatics 神秘的提交答案题. Day1 T2 IOI 熱の感染拡大 我们可以通过移动+旋转坐标系,使得第 1 个宫殿在 \((0,0)\) 处,且方向为 \(x\) 轴正方 ...

  2. 惠普CP1025后盖传感器松导致不停自检或打印中掉电, 跳闪三角灯

    上次修了离合器, 没出两星期又出问题了. CP1025这个型号就是出名的开机特别慢, 正常自检是1分钟, 但是前天我在给机器换完粉盒后, 自检似乎进入了死循环, 一直在自检. 周末才有空看看能不能解决 ...

  3. Java并发编程实例--11.在线程组中处理未检查异常

    第8个例子讲了如何在线程中捕捉未检查异常,本例将介绍如何在线程组中处理未检查异常. Task.java package com.dylan.thread.ch1.c11.task; import ja ...

  4. Go 项目的文件布局

    转自 kcq 的 https://github.com/golang-standards/project-layout https://github.com/golang-standards/proj ...

  5. xml开发笔记(一):tinyXml2库介绍、编译和工程模板

    前言   Qt开发Xml相关技术,使用到tinyxml2库.   TinyXML   TinyXML是一个简单的.小的C++的XML解析器,可以集成到其他程序中.它是ROS的标准XML解析器.  最新 ...

  6. django中如果不是第一次迁移的时候就配置AUTH_USER_MODEL(用来告知django认证系统识别我们自定义的模型类),那么该如何解决才能让django的认证系统识别且不会报未知错误?

    Django认证系统中提供的用户模型类及方法很方便,我们可以使用这个模型类,但是字段有些无法满足项目需求,如还需要保存用户的手机号,需要给模型类添加额外的字段. Django提供了django.con ...

  7. day04---系统重要文件

    系统重要的文件 /etc的重要文件 1./etc/sysconfig/network-scripts/ifcfg-eth0 [root@localhost ~]# cat /etc/sysconfig ...

  8. vue 项目npm run dev(启动)时报错The service was stopped

    vue项目yarn upgrade后vite build报错,如何项目也运行不起来了. 报错截图: 解决办法: 删除node_modules文件夹,然后执行yarn install重新生成心的node ...

  9. Django重点

    Django 简述python三大主流web框架 """ django 大而全,类似于航空母舰 但是有时候过于笨重 flask 小而精,类似于游骑兵(单行代码就可以起一个 ...

  10. 如何在矩池云上安装语音识别模型 Whisper

    如何在矩池云上安装语音识别模型 Whisper Whisper 是 OpenAI 近期开源的一个语音识别的模型,研究人员基于 680,000 小时的标记音频数据进行训练,它同时也是一个多任务模型,可以 ...