Object-CiOS,object-c和swift开发码神之路:Object-C篇

附录 Objective-C 运行时

2017-05-30  本文已影响13人  Sober_DeTong
Objective-C 运行时

运行时(runtime)这个术语有多重的意思。到目前为止,我们用它形容应用在用户电脑中运行的一段时间。运行时可以和编译时(compile-time)做对比,编译时是在程序运行之前,还在使用Xcode构建程序的一段时间。
Objective-C运行时是OS X以及iOS系统执行Objective-C代码的一部分。它还负责动态的追踪记录哪些类存在,这些类定义了哪些方法,以及查看消息是否恰当地在对象之间传递。

内省

内省(introspection)是Objective-C运行时的一个特性:它能够让对象在程序运行的时候回答关于自身的问题。例如,这里有一个NSObject方法叫做respondsToSelector:

- (BOOL)respondsToSelector:(SEL)aSelector;

其中的一个实参是一个选择器(一个方法的名字)。如果对象实现了该选择器的名字的方法,就会返回YES;如果没有实现,就返回NO。使用respondsToSelector:即是内省的例子。

动态查找并执行方法

对象发送消息的时候,它会开始搜索要执行的方法。通常会从接收者的isa指针指向的类开始进行搜索,然后根据继承层级搜索,直到找到需要的方法。
动态查找并执行方法构成了Objective-C消息发送机制的基础,它也是Objective-C运行时的另一大特性。
查找并执行方法是C函数 objc_msgSend() 的工作。这个函数的实参是接收消息的对象,被执行的方法的选择器,以及这个方法的所有实参。

//  动态查找,并执行方法 objc_msgSend
NSString *nameString = @"miky";
NSString *capsName = objc_msgSend(nameString, @selector(uppercaseString));
NSLog(@"%@",capsName);
类以及继承层级的管理

Objective-C 运行时不仅负责记录正在使用哪些类,还负责记录那些包含到程序中的库以及框架使用的类。

#import <Foundation/Foundation.h>
#import <objc/message.h>
#import <objc/runtime.h>

//  获取给定类的继承层级
//  这个函数会获取一个类对象后得到它的父类,然后继续获取父类的父类,直到最后再也没有父类。通常,最后的类会是NSObject。
NSArray *BNRHierarchyForClass(Class cls) {
    //  声明一个数组用来保存类及其父类组成的列表,创建一个层级
    NSMutableArray *classHierarchy = [NSMutableArray array];
    //  继续追踪继承层级,知道再也没有父类
    for (Class c = cls; c != Nil; c = class_getSuperclass(c)) {
        NSString *className = NSStringFromClass(c);
        [classHierarchy insertObject:className atIndex:0];
    }
    return classHierarchy;
}

//  获取给定类的方法列表
//  方法(Method)方法是一类结构的名字,这类结构的成员包括方法的选择器(SEL类型的变量)以及一个函数指针(function pointer)
//  函数指针指向执行程序中内存数据段的一大块代码。这个函数指针是IMP类型的变量。
NSArray *BNRMethodsForClass(Class cls) {
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(cls, &methodCount);
    NSMutableArray *methodArray = [NSMutableArray array];
    for (int m = 0; m < methodCount ; m++) {
        //  获取当前的方法
        Method currentMethod = methodList[m];
        //  获取当前方法的选择器
        SEL methodSelector = method_getName(currentMethod);
        //  给数组增加字符串表达式
        [methodArray addObject:NSStringFromSelector(methodSelector)];
    }
    return methodArray;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //  创建一个字典数组,每个字典都会保存类的名称、层级以及给定类的方法列表
        NSMutableArray *runtimeClassesInfo = [NSMutableArray array];
        
        //  声明一个变量,用来保存注册表的数量。
        unsigned int classCount = 0;
        //  创建一个指针指向应用当前加载的所有注册类的列表
        //  通过引用返回注册类的数量
        Class *classList = objc_copyClassList(&classCount);
        
        //  列表单中的每个类 遍历类列表
        for (int i = 0; i < classCount; i++) {
            //  将类的列表作为一个C语言数组处理,获取其中的一个类
            Class currentClass = classList[i];
            
            //  将类的名称作为字符串处理
            NSString *className = NSStringFromClass(currentClass);
            //  输出类的名称
//            NSLog(@"className : %@",className);
            
            NSArray *hierarchy = BNRHierarchyForClass(currentClass);
            NSArray *methods = BNRMethodsForClass(currentClass);
            NSDictionary *classInfoObject = @{@"classname" : className,
                                              @"hierarchy" : hierarchy,
                                              @"methods" : methods};
            [runtimeClassesInfo addObject:classInfoObject];
        }
        //  现在已经不需要这个类列表的缓存区了,释放它
        free(classList);
        //  按照字母顺序给这些类排序,打印出来
        NSSortDescriptor *alphaAsc = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
        NSArray *sortedArray = [runtimeClassesInfo sortedArrayUsingDescriptors:@[alphaAsc]];
        
        NSLog(@"There are %ld classes registered with this program`s Runtime",sortedArray.count);
        NSLog(@"%@",sortedArray);
    }
    return 0;
}

objc_copyClassList()函数会返回一个由指定类对象的指针组成的C数组。
按照惯例,调用名字中包含“copy”或“create”的函数时所使用的内存,例如objc_copyClassList()函数,如果不再需要,就必须释放。这种情况我们称之为创建规则(create rule)。与之类似,如果调用名字中包含“get”的函数时所使用的内存不归你所有,就不必释放。这种情况我们称之为获取规则(get rule)。注意这两条规则和手动内存管理中使用的规则类似。

KVO 的工作原理

KVO 是苹果公司的API依赖于运行时函数的另一个例子。在第36章讨论KVO 的时候,如果对象使用存取器,被观察的对象可以自动通知属性中的变化。
运行时,如果向某个对象发送addObserver:forKeyPath:option:context:消息,那么这个方法可以:

例如,一个类的location属性的存方法代码如下:

- (void)setLocation:(NSPoint)location {
    _location = location;
}

在新的子类中,存取器会被覆盖如下:

- (void)setLocation:(NSPoint)location {
    [self willChangeValueForKey:@"location"];
    [super setLocation:location];
    [self didChangeValueForKey:@"location"];
}

子类的存取器实现会调用原始类的实现,然后将它们用简明的KVO通知消息封装起来。这些新的类以及方法都会在运行时使用Objective-C运行时函数定义。

对类新增观察者之后,会有一个新的子类:

className = "NSKVONotifying_类名";
hierarchy = (NSObject, 类名 ,"NSKVONotifying_BNRTowel");
methods = ("setLocation:" , class , dealloc , "_isKVOA");
上一篇 下一篇

猜你喜欢

热点阅读