Object-C(以后简称OC)中有id类型,相对于明确定义类型的静态类型,称为动态类型。
使用动态类型,配合多态(不同类型拥有同名方法),动态绑定(运行时决定实际调用的方法)可以将很多判断延迟到运行时决定,比如运行时才决定对象是某个类型,决定调用哪个类型的方法等。这样提高了灵活性,但是同样带来了风险,所以和支持动态类型的其他面向对象的语言一样,需要提供机制来做运行时判断,这样可以一定程度规避运行时错误。
看到一个动态类型的实例对象,我们都会习惯提出的问题:
1. 这个对象是属于某个类么?或者这个对象的类型是什么?
2. 这个对象支持接收某个消息么?或者说这个对象的类型定义了某个方法么?
等等...
要了解这些问题的答案,我们看看头文件NSObject.h中的protocol定义中的一些代码片段:
@protocol NSObject
- (Class)superclass;
- (Class)class;
- (id)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)respondsToSelector:(SEL)aSelector;
@end
现在我们都看到了减号‘-’,就是说这些都是实例对象可以发送的消息,我们挨个过一遍:
- (Class)superclass;
Class是一个指针结构定义,OC系统会从该指针中获得类型信息,比如你写如下代码:
NSString * obj = @"nsstring";
Class objClass = [obj class];
id objClass1 = [obj class];
调试一下,你会看到objClass的值是(class)_NSCFConstantString,而objClass1的值是一个指针值,类似(id)0x7fff779d73b8(这是一个在64位上执行的结果),objClass的值是OC系统从objClass1的指针值中获取的,因为OC系统会跟踪每个实例对象所属类型的对应关系。
所以你可以用类似于下面的代码来判断两个对象实例的父类型是否一致:
if ([obj1 supperclass] == [obj2 supperclass]) {//do sth...}
- (Class) class同理,用于获取实例对象的类型信息。
- (id) self,用于获取本身实例的指针值。
讲selector前,我们先看看下面两个消息定义:
- (BOOL)isKindOfClass:(Class)aClass
- (BOOL)isMemberOfClass:(Class)aClass;
IsKindOfClass顾名思义,是说对象的类型是否是某个类型,那么下面的代码中的两个布尔值都应该是YES。
NSString * str = "nsstring";
Class strClass = [str class];
Class strSuperClass = [str superclass];
BOOL isNSString = [str isKindOfClass: strClass];
BOOL isNSObject = [str isKindOfClass: strSuperClass];]
IsMemberOfClass,从字面意思看,貌似是问对象是不是某个类型的成员?有些别扭 :(,我们来看看代码执行结果来判断实际用途:
NSString * str = "nsstring";
Class strClass = [str class];
Class strSuperClass = [str superclass];
BOOL isNSStringMember = [str isMemberOfClass: strClass];
BOOL isNSObjectMember = [str isMemberOfClass: strSuperClass];]
执行结果是:isNSStringMember为YES,isNSObjectMember为NO。所以看起来isMemberOfClass用来鉴别对象是不是直接的某类型的实例,而不是继承关系的实例。
所以个人认为,这两个方法名字应该改动如下:
isKindOfClass -> isInstanceOfClass
isMemberOfClass -> isDirectInstanceOfClass
当然,源代码定义接口名称时候,应该是有所考虑的,暂且认为自己没有理解原作者的用意吧。
下面我们看跟4个selector相关的方法定义:
- (BOOL)respondsToSelector:(SEL)aSelector;
这个方法名称很明确,判断某个对象是否响应指定的selector,这里我们看看看SEL类型的selector是什么?在objc.h中有如下定义:
typedef struct objc_selector *SEL;
和Class类似是一个结构指针,该结构是OC系统用来保存方法定义信息的,OC系统通过该指针可以查询到方法的签名信息和类属信息。OC中提供了关键之@selector来获取一个方法的SEL指针值。那么下面代码中isOK是YES,因为NSString是NSObject,所以必须能够响应init方法。
NSString * str = @"nsstring";
BOOL isOK = [str respondsToSelector: @selector (init)];
- (id)performSelector:(SEL)aSelector;
performSelector有三个变体,第一个是没有参数的,看看下面的代码:
NSString * str = @"nsstring";
SEL message = @selector(hash);
id var = [str performSelector: message];
id var1 = [str hash];
NSString对象是NSObject对象,因此肯定可以执行hash方法,那么var和var1的值必须是相等的,因为对于同一个对象取哈西值多次,每次值都应该是一样的。
另外两个performSelector同理,只是适应于方法签名有一个参数和有两个参数的情况。假设add方法定义如下:
- (id) add: (id) number;
那么SEL应该是像下面这样获取的:
SEL addMessage = @selector(add:);
id var = [number1 performSelector:add withObject:number];
打完收工,OC的方块发送消息的方式写着写着就慢慢习惯了~~