附录 Objective-C 运行时

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:消息,那么这个方法可以:

  • 决定被观察对象的类,并使用objc_allocateClassPair()函数给这个类定义一个新的子类。
  • 改变对象的isa指针,让它指向新的子类(高效改变对象的类型)。
  • 覆盖被观察对象的存取器,发送KVO消息。

例如,一个类的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");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容