在此处先留一个笑话,当再次回顾的时候可以有一个轻松的心情来阅读,纠错。同时也十分欢迎大神们、大牛们指出错误,我的膝盖随时准备着。
笑话:昨儿跟女朋友去鬼屋玩,刚进去呢,女朋友就被那些人工制造的恐怖气氛给吓哭了,真是没用的家伙,哪像我,直接被吓晕了,被工作人员抬了出来……
一、之间的关系,以及联系
1、对象
o-c中每一个对象都是一个类的实例,当我们alloc的时候就创建出一个类的实例。在此先引入一个叫做isa的东西,其本质为一个指针。它存在于每一个实例对象中,并且是对象的第一个实例变量,指向的对象为创建它的类。
2、类
每一个类描述了实例的特点,比如属性或成员列表、成员函数列表等。把它对应在我们平时写的代码中,就是初始化一个对象时调用的【Class alloc】;的那个Class。每一个类实际上是一个结构体,结构体中也包含一个isa指针,也是作为第一个实例变量。当我们使用【Class alloc】;这种方式给一个对象发送消息的时候,初始化出来的实例对象能够接收到的消息(方法),保存在成员函数列表中,此列表保存在类中(下面代码的objc_method_list **methodLists)。下面是类结构体中的模样:
struct objc_class { Class isa; struct objc_class super_class; 父类 const char *name; 类名字 long version; 版本信息 long info; 类信息 long instance_size; 实例大小 struct objc_ivar_list *ivars; 实例参数链表 struct objc_method_list **methodLists; 方法链表 struct objc_cache *cache; 方法缓存 struct objc_protocol_list *protocols; 协议链表};
在类的结构体中它的成员变量从isa指针开始一次排序,当我们初始化出一个对象的时候该结构体是无法动态改变的,也就无法给类动态的给类添加成员变量。但是对象的方法保存的位置和成员变量保存的位置是不一样的,它在类的可变区域。方法的定义链表如上所示是一个objc_method_list **methodLists指针的指针,通过动态的修改该指针指针的值,就可以实现添加成员方法。类目就是这么实现的,同时也说明了为什么类目没有办法添加成员变量(通过runtime可以动态的为一个类添加成员变量,但是并没有改变类的内存结构)。
3.元类
在2中每个类中有一个isa指针(类也是一个对象),它指向的地方就是此处--元类(metaclass)。元类中保存了类的类方法的方法列表,当一个类方法被调用的时候,元类会查找它本身是否有该类方法的实现(如果没有,则该元类会向它的父类查找,此处先暂且不讨论继承关系)。
4.根元类
元类也是一个对象,是对象它就存在实例化出来它的类,元类中也有isa指针。元类的isa指针指向的就是根元类(root meaclass)。根元类的也有isa指针,但它isa指针指向的是自己,到此所有的isa指针已经完结。开发中我们基本上用不到此处。
5.继承
类的类方法在元类中定义保存,而方法的调用为如果类中没有方法的实现就继续前往它的父类中寻找。所以我们在实现继承的时候,一个继承操作,它操作的不仅仅是类和类之间的继承。该类的元类也会继承被继承类的元类。
二、内存中存在形式和位置
第一部分是几个基本类之间的联系,第二部分疏导下当消息发送过程中几个类之间的作用和程序运行起来类所在内存中的位置。
1、消息发送时相互之间的配合
当我们使用消息发送机制发送消息的时候,此时方法的响应会先判断此方法是否存在于类中的方法列表中,而方法在链表中的存在是以Method的形式存在于链表中的。Method并不是直接能执行的方法,它是一个结构体,形式如下:
typedef struct objc_method *Method;typedef struct objc_ method { SEL method_name; //方法名称 char *method_types; //参数类型 IMP method_imp; //方法的具体实现的函数指针};
如果一切都满足,则会执行imp指针指向内存的方法。
2.各个类在内存中存在的位置
a、我们在创建一个对象: 类 *p = 【类 new】;的时候,在堆内存中就已经存在了一个该对象对应的类即实例对象isa指针指向的类。
b、在对象创建成功后,在栈区会有一个指针p,,堆里会多出一个类的实例对象。栈区里的p指针指向堆区里的类的实例对象中isa的地址,类的实例对象中的isa指针指向创建该实例对象的类。
c、当执行【】;消息调用的时候,此时是从栈区p开始,找到堆区的实例对象,实例对象通过isa指针找到堆区的里创建它的类,再在类的方法链表中判断消息(方法),当响应消息(执行方法)时,则会通过SEL指针找到代码区的方法实行,随后执行。注意的一点是如果是首次执行方法,会把方法加载到缓存中,当以后再执行此方法的时候,就会直接执行。