Through the Chaos!

==================> 知我罪我,唯有春秋!

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 指针,这个指针指向类对象的类,我们称为元类。

对象、类、元类的关系如下图: Class&MetaClass.001.jpg 因此,在循环中,首先获得了 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)

Comments

comments powered by Disqus