OC运行时小结之一
运行时是OC实现面向对象的保证,而且还可以实现各种各样的“黑魔法”。但是要注意的是,这些“黑魔法”不应该多用, 因为苹果会改变运行时的底层实现原理,虽然对外的接口没有变化,但是内部的实现会经常变化。
实际开发中,要用到运行时的地方很少,但是很多地方没有运行时的概念,就很难准确理解真正的含义。比如说下面的例子:
(其中 Person 为一个继承自 NSObject 的类)
NSLog(@"%d %d", [(id)[NSObject class] isKindOfClass:[NSObject class]],
[(id)[Person class] isKindOfClass:[Person class]]);
打印输出为:1和0。这样的结果看起来很奇怪,为何相同的形式会有不同的结果呢?我们可以从运行时的角度查看原因。
在苹果的开源网址提供了运行时的实现代码,其中 isKindOfClass 的实现为:
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
再来看 class 方法的实现:
- (Class)class {
return object_getClass(self);
}
以及 object_getClass 的实现:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
也就是说在 isKindOfClass 的内部循环中,首先获取传入消息接受者对应的类。而类实际上也是一个对象,那么
这个“类对象”中也有 isa 指针,这个指针指向类对象的类,我们称为元类。
对象、类、元类的关系如下图:
因此,在循环中,首先获得了 NSObject 类的元类,然后再循环地获取元类的父类后又回到了 NSObject 类本身,因此
循环中的判断条件满足;但是使用 Person 类时,会先在元类之间跳转,最后跳到 NSObject 类,然后是 nil ,一直没有
满足提前返回的条件。
在关于 isKindOfClass 方法的文档说明中,使用了一个 NSMutableArray 的例子来演示不要使用 isKindOfClass 来判断
接受者是否可变,但是这个例子似乎是错误的,我们有例子:
NSData *data = [NSData data];
NSData *dataM = [NSMutableData data];
NSString *string = [NSString string];
NSString *stringM = [NSMutableString string];
NSArray *array = [NSArray array];
NSMutableArray *arrayM = [NSMutableArray array];
NSDictionary *dict = [NSDictionary dictionary];
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
NSSet *set = [NSSet set];
NSMutableSet *setM = [NSMutableSet set];
NSArray *objects = @[
data,
dataM,
string,
stringM,
array,
arrayM,
dict,
dictM,
set,
setM
];
for (id obj in objects) {
for (Class testClass = object_getClass(obj); testClass;
testClass = [(id)testClass superclass]) {
NSLog(@"%@", testClass);
}
NSLog(@"-------------");
}
同样有输出:
2015-08-23 15:21:37.139 Runtime[1958:252674] _NSZeroData 2015-08-23 15:21:37.141 Runtime[1958:252674] NSData 2015-08-23 15:21:37.142 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.142 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.142 Runtime[1958:252674] NSConcreteMutableData 2015-08-23 15:21:37.142 Runtime[1958:252674] NSMutableData 2015-08-23 15:21:37.143 Runtime[1958:252674] NSData 2015-08-23 15:21:37.143 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.143 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.143 Runtime[1958:252674] __NSCFConstantString 2015-08-23 15:21:37.144 Runtime[1958:252674] __NSCFString 2015-08-23 15:21:37.144 Runtime[1958:252674] NSMutableString 2015-08-23 15:21:37.144 Runtime[1958:252674] NSString 2015-08-23 15:21:37.145 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.145 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.145 Runtime[1958:252674] __NSCFString 2015-08-23 15:21:37.145 Runtime[1958:252674] NSMutableString 2015-08-23 15:21:37.146 Runtime[1958:252674] NSString 2015-08-23 15:21:37.146 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.146 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.146 Runtime[1958:252674] __NSArrayI 2015-08-23 15:21:37.147 Runtime[1958:252674] NSArray 2015-08-23 15:21:37.147 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.147 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.148 Runtime[1958:252674] __NSArrayM 2015-08-23 15:21:37.148 Runtime[1958:252674] NSMutableArray 2015-08-23 15:21:37.148 Runtime[1958:252674] NSArray 2015-08-23 15:21:37.149 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.149 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.149 Runtime[1958:252674] __NSDictionaryI 2015-08-23 15:21:37.150 Runtime[1958:252674] NSDictionary 2015-08-23 15:21:37.150 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.150 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.150 Runtime[1958:252674] __NSDictionaryM 2015-08-23 15:21:37.151 Runtime[1958:252674] NSMutableDictionary 2015-08-23 15:21:37.151 Runtime[1958:252674] NSDictionary 2015-08-23 15:21:37.151 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.151 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.151 Runtime[1958:252674] __NSSetI 2015-08-23 15:21:37.152 Runtime[1958:252674] NSSet 2015-08-23 15:21:37.152 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.152 Runtime[1958:252674] ------------- 2015-08-23 15:21:37.152 Runtime[1958:252674] __NSSetM 2015-08-23 15:21:37.153 Runtime[1958:252674] NSMutableSet 2015-08-23 15:21:37.153 Runtime[1958:252674] NSSet 2015-08-23 15:21:37.153 Runtime[1958:252674] NSObject 2015-08-23 15:21:37.153 Runtime[1958:252674] -------------
因此,消息传递 [array isKindOfClass:[NSMutableArray class]] 的返回值应该为0,也就是
说我们可以根据这个方法判断一个数组是否可变,但是上边有一个特殊的例子—— NSString 。
在输出中,我们可以看到,在遍历父类的过程中, NSString 遍历过 NSMutableString ,因此,无法使用
isKindOfClass 方法来判断一个字符串是否为可变字符串。实际上,想要判断类族中的对象是否为可变
对象,最好还是使用抛出异常的方法。
运行时的实现原理,还有一些类族的内部实现有可能会发生变化,因此上边的例子仅为参考。(我使用的 Xcode版本为7.0beta4)