前言
今天整理了下自己电脑里的一些碎片笔记,时间有限只整理了这篇文章——类的本质,大家可以进行参考。
1.本质
- 类的本质其实也是一个对象(类对象)
- 程序中第一次使用该类的时候被创建,在整个程序中只有一份。
- 此后每次使用都是这个类对象,它在程序运行时一直存在。
- 类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本,继承层次,以及消息与函数的映射表等
- 类对象代表类,Class类型,对象方法属于类对象
- 如果消息的接收者是类名,则类名代表类对象
- 所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象
2.如何获取类对象
- 通过实例对象
格式:[实例对象 class];
如: [dog class];
- 通过类名获取(类名其实就是类对象)
格式:[类名 class];
如:[Dog class]
3.类对象的用法
- 用来调用类方法
[Dog test];
Class c = [Dog class];
[c test];
- 用来创建实例对象
Dog *g = [Dog new];
Class c = [Dog class];
Dog *g1 = [c new];
4.类对象的存储
存储.png
5.OC实例对象、类对象、元数据、之间关系
- Objective-C是一门面向对象的编程语言。
- 每一个对象 都是一个类的实例。
- 每一个对象 都有一个名为isa的指针,指向该对象的类。
- 每一个类都描述了一系列它的实例的特点,包括成员变量的列表,成员函数的列表等。
- 每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。
- 在XCode中按Shift Command O打开文件搜索框,然后输入NSObject.h和objc.h,可以打开 NSObject的定义头文件,通过头文件我们可以看到,NSObject就是一个包含isa指针的结构体,如下图所示:
NSObject.h
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
代码语言:javascript复制objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
- 按照面向对象语言的设计原则,所有事物都应该是对象(严格来说 Objective-C并没有完全做到这一点,因为它有象int,double这样的简单 变量类型)
- 在Objective-C语言中,每一个类实际上也是一个对象。每一个类也有一个名为isa的指针。每一个类都可以接受消息,例如[NSObject new],就是向NSObject这个类发送名为new的消息。
- 在XCode中按Shift Command O,然后输入runtime.h,可以打开Class的定义头文件,通过头文件我们可以看到,Class也是一个包含isa指针的结构体,如下图所示。(图中除了isa外还有其它成员变量,但那是为了兼容非2.0版的Objective-C的遗留逻辑,大家可以忽略它。)
runtime.h
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- 因为类也是一个对象,那它也必须是另一个类的实例,这个类就是元类 (metaclass)。
- 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。
- 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
- 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说?到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
- 由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
- 下面这张图或许能够 让大家对isa和继承的关系清楚一些
其中
:实线箭头代表类的继承关系,比如EOCStudent继承自EOCPerson,也就是说,EOCStudent是EOCPerson的子类。就可以用实线表示这种继承关系:EOCStudent —>EOCPerson。
虚线箭头代表对象和类的从属关系,比如一个对象student属于EOCStudent类,也就是说,student是EOCStudent的实例。就可以用虚线表示这种从属关系:student—>EOCStudent。
引用《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》中的一段话:superclass指针确定了继承关系,而isa指针描述了实例所属的类。通过这张布局关系图即可进行“类型信息查询”。我们能查出对象是否能够响应某个选择子(selector),是否遵从某项协议,并且能够看出某对象位于集成体系的哪一部分。
继承/从属关系图
- 上图中,最让人困惑的莫过于Root Class了。在实现中,Root Class是指
- NSObject,我们可以从图中看出:
- NSObject类对象包括它的对象实例方法。
- NSObject的元对象包括它的类方法,例如new方法。
- NSObject的元对象继承自NSObject类。
- 一个NSObject的类中的方法同时也会被NSObject的子类在查找方法时找到。
当对象收到消息时,消息函数首先根据该对象的isa 指针找到该对象所对应的类的方法表,并从表中寻找该消息对应的方法selector。如果找不到,objc_msgSend 将继续从父类中寻找,直到NSObject 类。一旦找到了方法选标, objc_msgSend 则以消息接收者对象为参数调用,调用该选标对应的方法实现。调用方式:
objc_msgSend(receiver, selector, arg1, arg2, ...)
这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。 为了加快消息的处理过程,运行时系统通常会将使用过的方法选标和方法实现的地址放入缓存中。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。消息函数会首先检查消息接收者对象对应的类的缓存(理论上,如果一个方法被使用过一次,那么它很可能被再次使用)。如果在缓存中已经有了需要的方法选标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。
6.如何查询类型信息
可以使用“类型信息查询方法”来查询类的继承体系。其中,“isMemberOfClass:”可以判断对象是否是特定类的实例。而”isKindOfClass:”可以判断对象是否是某个类或者其派生子类的实例。而本质上,这两个类型信息查询方法是使用对象的isa指针获取对象所属的类(因为类对象也是对象,所以也有isa指针,该指针指向元类,也就是类对象所属的类),然后通过类继承体系中的superclass指针在继承体系中游走。Objective-C与其他语言不同,Objective-C必须查询类型信息,才能完全了解对象的真实类型。
另外,需要注意的是,我们从集合对象(collection)中获取的对象,通常会用到这两个查询类型信息的方法。因为从集合对象中取出来的对象不是强类型的(strongly typed),其类型通常是id。回想一下,我们从一个数组中取出来的对象,其返回值是id类型的。这就是为什么我们可以在这个取出来的对象身上通过中括号”[ ]”的形式调用任何方法,却不能通过点语法来调用方法。不过,为了安全起见,如果涉及到对集合对象中的某个对象进行操作,我们还是需要做一下类型判断比较好。如下所示:
代码语言:javascript复制 for (id object in array) {
if (object isKindOfClass:[NSString class]) {
// object is an instance of NSString
}
}
当然,也可以用比较类对象是否等同的方法来判断对象是否属于某个类。若是如此,那就应该使用==操作符,而不要使用比较Objective-C对象使常用的“isEqual:”方法。因为==操作符比较的是指针是否相等,也就是比较内存地址是否相同。而"isEqual:"比较的是两个Objective-C对象的值是否相等。此处用==操作符,原因在于,类对象类对象是“单例”,在应用程序范围内,每个类的Class仅有一个实例,在整个内存中仅有一份(因为 (void)load方法和 (void)initialize只被调用一次)。所以也可以用下面这种方进行比较:
代码语言:javascript复制if ([object class] == [EOCSomeClass class]) {
// object is an instance of EOCSomeClass
}
虽然调用class方法和isKindOfClass:方法都可以查询一个对象的类型。但是还是建议使用后者。下面笔者引用《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》中的一段话来进行解释:
虽然使用"
class方法
"也可以查询对象的类型信息。但是还是建议使用isKindOfClass:
这样的类型信息查询方法。因为后者可以正确处理那些使用了消息传递机制对象。比方说某个对象可能会把其的所有选择子(selector)都转发给另一个对象(开启了消息转发功能)。这样的对象叫做”代理(proxy)“,此种对象所属的类均以NSProxy为根类(root class)。通常情况下,如果在此种代理对象上调用class方法,那么返回的是代理对象本身(NSProxy的子类),而非接受代理的对象所属的类。然而,若是改用“isKindOfClass:”这样的类型信息查询方法,那么代理对象就会把这条消息转给“接受代理的对象(proxy object)”。也就是说,这条消息(指isKindOfClass:)的返回值与直接接受代理的对象身上查询其类型信息所得的结果相同。因此,这样查出来的类对象与直接通过class方法所返回的那个类对象不同,class方法所返回类表示发起代理的对象
,而非接受代理的对象
。