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)