线程的内存模型,进程的内存模型
Objective-C是一种通用的、高级的、面向对象的编程语言。它扩展了标准的ANSI C编程语言,并在ANSI C中加入了Smalltalk风格的消息传递机制。
可以理解为Objective-C=C Runtime,这是C语言拥有面向对象功能的驱动力,是iOS开发中的核心概念。我们可以在苹果的开源运行时(最新版objc4-779.1.tar.gz)中找到Objective-C对象模型的一些实现细节。
NSObject的实现:OC中几乎所有的类都继承自NSObject,OC的动态也是通过NSObject实现的,所以要从NSObject入手。
在NSObject.h运行时源代码中,我们可以找到NSObject的定义:
@ interface ns object ns object { Class ISA OBJC _ ISA _ avail ability;}可以看到NSObject中有一个指向Class的isa,其中Class的定义在objc.h:
///表示Objective-C类的不透明类型。
typedef struct objc _ class * Class
///表示类的实例。
结构对象_对象{
class _ Nonnull ISA OBJC _ ISA _ avail ability;
};
Objc_class代表类对象,objc_object代表实例对象,objc_object的isa指向objc_class。可以得出结论,实例对象的isa指向类(类对象)。其实类(objc_class)也有isa属性,那么它指向什么呢?
元类(Meta Class)这里运行时为了设计的统一性,引入了元类的概念。
当调用对象的实例方法时,方法的实现通过对象的isa在类中获得。当一个类对象的类方法被调用时,该方法的实现通过该类的isa在元类中获得。
objc_class的Isa指向元类,甚至元类也有isa指针,指向根元类。实例、类对象、元类和根元类之间的关系如下图所示:
和元类形成了一个完整的闭环,其中有两个关系需要注意:
类的isa都指向根元类,根元类指向自身。根元类继承了根类(NSObject)的ns object)object 1.0数据模型。我们可以在runtime.h中检查objc_class的定义
结构对象_类{
class _ Nonnull ISA OBJC _ ISA _ avail ability;
#如果!__OBJC2__
class _ Nullable super _ class objc 2 _ UNAVAILABLE;
const char * _ non null name objc 2 _ UNAVAILABLE;
长版OBJC2 _不可用;
long info OBJC2 _ UNAVAILABLE
long instance _ size objc 2 _ UNAVAILABLE;
struct objc _ ivar _ list * _ Nullable ivars objc 2 _ UNAVAILABLE;
struct objc _ method _ list * _ Nullable * _ Nullable method lists objc 2 _ UNAVAILABLE;
struct objc _ cache * _ Nonnull cache objc 2 _ UNAVAILABLE;
struct objc _ protocol _ list * _ Nullable protocols obj C2 _ UNAVAILABLE;
#endif
} OBJC2 _不可用;
/*使用“Class”而不是“struct objc_class *` */
注意这两个宏命令:__OBJC2__和OBJC2_UNAVAILABLE,都是为了表示当前的objc_class结构是OBJC2之前的结构设计,也就是Objc1.0的设计
从objc_class的定义中我们可以看到,它包含了超类的指针(super_class)、类名(name)、实例大小(instance_size)、objc_ivar_list成员变量列表的指针(ivars)和指向objc_method_list指针的指针(methodLists)。
注意*methodLists是一个指向方法列表的指针,您可以动态修改*methodLists的值来添加成员方法。这也是Category实现的原理,也解释了为什么Category不能添加属性。
剩下的对象缓存代表函数的缓存列表,对象协议列表代表协议列表。
目标语言历史我在网上查资料的时候发现关于运行时间的文章非常多,但提示数据模型在OC1.0和2.0之间区别的非常少,其实这一点很重要的。这也是为什么我将这段标题命名为目标-C1.0数据模型的原因。
这里补一点目标-丙语言的发展历史(维基百科):
目标-C1.0即目标-丙由楼梯石级公司的布莱德考克斯(布拉德考克斯)和汤姆洛夫(汤姆洛夫)在1980 年代发明。它是(同地面控制中心)地面控制中心的一个前端,它可以编译混合C与目标-丙语法的源文件106 .目标-丙是C的扩展,类似于目标-丙是C的扩展。
目标-C2.0在2006年七月苹果全球开发者会议中,苹果宣布了"目标-C 2.0英寸的发布,其增加了"现代的垃圾收集,语法改进,运行时性能改进,以及64位支持"。
目标2.0数据模型可以在objc-runtim-new.h文件找到新版对对象_类的数据模型定义:
结构对象类:对象对象
//Class ISA;
类超类;
cache _ t cache//以前的缓存指针和虚函数的类创建一个表
类_数据_位_t位;//class_rw_t *加上自定义rr/alloc标志
class_rw_t *data()常量{
返回位。data();
}
}
结构对象_对象{
私人:
isa _ t isa
}
联合isa_t
{
isa_t() { }
isa_t(uintptr_t值) :位(值){ }
清屏类;
uintptr_t位;
}
会发现对象_类不再是一个单独的结构体,而是继承于对象对象,对象对象内部的伊萨变成了isa_t的联合体。
classdatabits_t我们再回来看类中的其他属性,之前表示类的属性、方法、以及遵循的协议都放在了类_数据_位_t中,更准确的说是放在了第一课。
struct class_data_bits_t {
朋友对象_类
//值是上面的快速标志.
uintptr_t位;
class_rw_t* data()常量{
return(class _ rw _ t *)(bits FAST _ DATA _ MASK);
}
}
struct class_rw_t {
//注意符号化知道这个结构的布局。
uint32_t标志;
uint16_t版本;
uint16_t见证;
const class _ ro _ t * ro
方法_数组_t方法;
属性_数组_t属性;
协议_阵列_测试协议;
}
struct class_ro_t {
uint32_t标志;
uint32 _ t instanceStart
uint32 _ t实例大小
#ifdef __LP64__
uint32_t保留;
#endif
const uint8 _ t * ivarLayout
常量字符*名称
method _ list _ t * baseMethodList
协议_列表_ t *基础协议
const ivar _ list _ t * ivars
const uint8 _ t * weakIvarLayout
property _ list _ t *基本属性;
}
这里面引入了类_rw_t和class_ro_t(读写,只读)两个结构体。可以看到类_rw_t是包含一个常量指针ro,结构体为class_ro_t。这里存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。在中国运行时的时候会调用实现类方法,将class_ro_t传入class_rw_t,所以新版的动态性是通过这种方式实现的。
cache_t结构cache_t
静态bucket _ t *空桶();
struct bucket _ t * buckets();
mask _ t mask();
mask _ t occupated();
}
struct bucket_t {
//IMP-first对arm64e ptrauth更好,对arm64也不差。
//SEL-first更适合armv7*和i386以及x86_64。
#if __arm64__
explicit _ atomic uintptr _ t _ imp
explicit _ atomic SEL _ sel
#否则
explicit _ atomic SEL _ sel
explicit _ atomic uintptr _ t _ imp
#endif
}
Cache_t是objc_class中的缓存结构,通过bucket_t结构存储一些最近调用的函数。设置缓存的最大原因是OC是一种动态语言,函数的执行是通过消息调用来实现的。消息调用会先在当前类中查找方法列表,如果找不到,就会查找父类。直到检索NSObject找不到函数实现,才会进入消息转发流程。为了节省每次查找函数表的成本,发明了cache_t。我们从bucket_t的内联函数可以看出,缓存的SEL和IMP都是加载到内存中的。
方法_t结构方法_t {
SEL名称;
const char *类型;
MethodListIMP imp
结构SortBySELAddress:
public STD:binary _ function const method _ t
const方法_t,bool
{
bool运算符()(const method_t lhs,
const方法_t rhs)
{ return lhs . name RHS . name;}
};
};
这是函数的结构,它包含三个成员变量。SEL是方法名的名称。Types是一个类型代码,类型可以称为类型编码。IMP是一个函数指针,指向一个函数的具体实现。运行时消息传递和转发的目的是发现IMP和执行函数。
数据的比较。最后,Objc1.0和2.0的对比:
这两张图片引自沈晗的博客。
注意:由于引用的运行时版本不一致,可能会有一些差异,但都是一致的。
要深入分析ObjC中的方法,请参考链接。结构神经医院Objective-C运行时间为入院第一天—— isa和Class。
转载请联系作者获得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。