该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
第7章 运行时系统
7.4 动态绑定
动态绑定(dynamic binding):
是指在运行程序时(而不是在编译时)将消息与方法对应起来的处理过程;
在运行程序和发送消息前,消息和接收消息的对象不会对应;
因为许多接收器对象可能会实现相同的方法,调用方法的方式会动态变化;
也因此,是动态绑定实现了OOP的多态性;
使用动态绑定可以在不影响既有代码的情况下,将新对象和代码 连接或添加到系统中,从而降低对象之前的耦合度;
动态绑定应用于使用了动态类型的情况;
例如我们有如下的代码:(我们使用第三章的示例类)
id atom = [[C3Hydrogen alloc]initWithNeutrons:2];
[atom logInfo];
分析:
在执行这段代码时,运行时系统会确定变量atom的实际类型(通过动态类型:运行时系统会在运行时确定动态类型的实际类型);
然后通过动态绑定,使用消息选择器(loginfo)将该消息与接收器(atom对象)的实例方法对应起来;
在本例中:
变量atom的类型被设置为C3Hydrogen *,因此运行时系统会搜索C3Hydrogen类的实例方法loginfo;
如果找不到,则会在C3Hydrogen类的父类中继续寻找;
运行时系统会一直在类层次结构中寻找该实例方法,直到找到为止;
消息[atom loginfo]
|
|____>OC运行时系统进行如下处理:
1)确定消息接收器类型(动态类型)
2)确定实现方法(动态绑定);注意:无论是动态类型还是动态绑定都是一种功能,可别认为动态类型只是一种类型;
3)明确了类型,找到了方法,剩下的就是调用方法了;
动态绑定是OC的一种继承特性:
不需要任何特定的API;
使用动态绑定甚至可以将消息选择器设置为在运行程序时确定的变量;
7.5 动态方法决议
使用动态方法决议能够以动态方式实现方法;
使用OC中的@dynamic指令,可以告知编译器与属性关联的方法会以动态的方式实现;
@dynamic propertyName;//表示编译器须动态地生成该属性对应的方法
CoreData框架使用@dynamic指令,为管理对象类生成高效属性访问器方法和关系访问器方法;
NSObject类中含有以下两个方法:
resolveInstanceMethod:
resolveClassMethod:
它们能够以动态方式分别为指定的实例和类方法选择器提供实现代码;
你可以重写这些方法,以动态方式实现实例/类方法;
接下来看看这些方法的使用,来以动态方式为选择器实现方法;
以动态方式实现方法
接下来我们通过更新C7Calculator来以动态方式实现方法,来展示动态方法决议;
通过重写resolveInstanceMethod:类方法更新类的实现代码;
C7Calculator.m中的新增代码如下:
(Code1)
//
// C7Calculator.m
// 精通Objective-C
//
// Created by 花强 on 2017/11/28.
// Copyright © 2017年 花强. All rights reserved.
//
#import <objc/runtime.h>
#import "C7Calculator.h"
@implementation C7Calculator
-(NSNumber *)sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2{
NSLog(@"Invoking method on %@ object with selector %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
return [NSNumber numberWithInteger:([adder1 integerValue]+[adder2 integerValue])];
}
-(NSNumber *)sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2{//这个方法的第二个参数使用空参数
NSLog(@"Invoking method on %@ object with selector %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
return [NSNumber numberWithInteger:([adder1 integerValue]+[adder2 integerValue])];
}
id absoluteValue(id self , SEL _cmd , id value){
NSInteger intVal = [value integerValue];
if (intVal < 0) {
NSLog(@"para object class:%@\npara method SEL:%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
return [NSNumber numberWithInteger:(intVal * -1)];
}
return value;
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString * method = NSStringFromSelector(sel);
if ([method hasPrefix:@"absoluteValue"]) {
class_addMethod([self class],sel,(IMP)absoluteValue,"@@:@");
NSLog(@"Dynamic added instance method %@ to class %@",method,NSStringFromClass([self class]));
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
我们来分析一下这段代码:
首先我们需要一条运行时系统库的导入指令(#import <objc/runtime.h>);这样会将运行时系统的API添加到你编写的代码中;
这里class_addMethod()就是一个运行时系统API,使用它能够以动态方式将函数作为实例方法添加到类中;
class_addMethod([self class],sel,(IMP)absoluteValue,"@@:@");
该API的输入参数为添加方法的目标类、新方法的选择器、函数的地址和描述方法参数的数据类型的字符串;
这里,被添加为实例方法的函数为absoluteValue,接收类型为id的输入参数并返回类型id的结果,对应字符串'@@:@';
如果是返回值为空,无参数的函数可以表示为‘v@:’这样;
再看一下实现的函数id absoluteValue(id self , SEL _cmd , id value)
该函数的输入参数含有隐式参数self和_cmd,self用于设置接受对象,而_cmd用于设置方法的选择器;
我们来实际操作下:
C7Calculator * calculator = [[C7Calculator alloc]init];
id sum = [calculator performSelector:NSSelectorFromString(@"absoluteValue") withObject:@-2];
NSLog(@"%@",sum);
log:
2017-12-05 16:29:58.508652+0800 精通Objective-C[56598:18002143] Dynamic added instance method absoluteValue to class C7Calculator
2017-12-05 16:29:58.508763+0800 精通Objective-C[56598:18002143] para object class:C7Calculator
para method SEL:absoluteValue
2017-12-05 16:29:58.508851+0800 精通Objective-C[56598:18002143] 2
来看看这段代码:
这段代码以动态方式为新建方法创建了一个选择器:NSSelectorFromString(@"absoluteValue")
然后,使用该选择器调用了一个实例方法,通过动态方法决议可以在运行程序时 添加和调用 这个方法,并获得他的返回值;
当使用选择器的消息通过performSelector:withObject:方法被以动态方式调用时,OC运行时系统会将新方法添加到C7Calculator类中;
可以看到动态方法决议可用于在运行程序时向类中添加方法;
7.6 动态加载
OC程序通过 动态加载 功能可以根据需要加载可执行代码和源代码,而无需在启动程序时就加载程序的所有组件;
延迟加载(lazy-loading):
可执行代码(在加载前就链接好的)可以含有新的类,并使这些新的类在运行程序时整合到整个程序中;
这种程序代码和数据资源的延迟加载方式可以提高程序的整体性能,因为它降低了系统内存的需求;
该方式还提高了程序的可扩展性,因为能使新软件在不更改已存在程序的情况下,以动态方式将新增代码添加到程序中;
比如以动态方式加载软件的包bundle机制;
包是一种软件的交付机制;
包由具有标准层次结构的目录以及该目录中的可执行代码和源代码构成;
包可以包含可执行代码、图像、音频文件和其他类型的代码以及资源数据;
还含有一个运行时配置文件,即信息属性列表Info.plist;
包定义了组织与软件有关的代码和数据资源的基本结构,可分为一下几种类型:
1)应用程序包:
应用程序包 管理与进程(程序)有关的代码和数据资源;
2)框架包:
框架包 管理以动态方式共享的软件库及相关的数据资源(如头文件);
应用程序可以连接一个或多个框架,如Foundation框架;
3)可选加载包:
可选加载包(也称插件)是用于以动态方式加载自定义代码的包;
可以使用Foundation框架中的NSBundle类管理 包;
一个NSBundle对象就代表文件系统中的一个存储位置,该位置存储着可在程序中使用的代码和数据资源;
比如我们可以这样加载当前应用程序的信息属性列表(Info.plist)的路径;
NSBundle * bundle = [NSBundle mainBundle];
NSString * bundlePath = [bundle pathForResource:@"Info" ofType:@"plist"];
NSLog(@"Info plist path:%@",bundlePath);
log:
2017-12-05 17:46:36.815789+0800 精通Objective-C[57256:18059881] Info plist path:/Users/huaqiang/Library/Developer/CoreSimulator/Devices/3FB29963-72AE-4642-A3E0-5732A40C6267/data/Containers/Bundle/Application/2F0A7F42-1DDA-4B0E-A6B1-DAE8C78157C1/精通Objective-C.app/Info.plist
前面说到了使用包能够动态加载可执行代码,你可以这样做:
使用一个NSBundle对象,以动态的方式加载了一个框架包,再通过该框架创建一个实例;
NSBundle * testBundle = [NSBundel bundleWithPath:@"/Test.bundle"];
id tester = [[[testBundle classNamed:@"Tester"] alloc] init];
7.7 内省
前面我们提到“如检查动态设置类型的匿名对象属于哪个类”用的就是自省;
Foundation框架中的NSObject类的API含有非常多用于执行对象内省的方法;
使用这些方法能够以动态方式在程序运行时查询下列信息:
1)与方法有关的信息;
2)测试对象的继承性、行为和一致性的信息;
因为OC运行时行为与编译连接时行为差异大,所以对象内省就成了一种关键功能;
使用它可以避免运行时错误,如消息分派错误、对对象相等的错误假设以及其他问题;
[object isKindOfClass:[ClassA class]];
这个NSObject的实例方法可以用来测试接收器是ClassA类的实例还是其子类的实例;
[object respondsToSelector:@selector(selector)];
这个方法可以检查某个对象是否会对选择器做出回应,即该对象是否实现了或继承了能够对指定消息做出回应的方法;
[object conformsToProtocol:@protocol(protocol)];
检查对象是否遵循了指定的协议;
我们可以这样为选择器提取方法签名:
NSMethodSignature * signature = [atom methodSignatureForSelector:@selector(massNumber)];
NSLog(@"signature:%ld",[signature numberOfArguments]);
log:
2017-12-05 18:08:40.597009+0800 精通Objective-C[57482:18078532] @
2017-12-05 18:08:40.597102+0800 精通Objective-C[57482:18078532] :
2017-12-05 18:08:40.597200+0800 精通Objective-C[57482:18078532] returnType:Q ,returnLen:8
2017-12-05 18:08:40.597329+0800 精通Objective-C[57482:18078532] signature:<NSMethodSignature: 0x60000026a840>
2017-12-05 18:08:40.597446+0800 精通Objective-C[57482:18078532] signature:2
这些都是使用NSObject类的方法进行对象内省的简单示例,后续我们还会介绍NSObject类的API;
7.8 小结
本章介绍了OC运行时系统的特性和关键组件;
本章要点:
1)使用OC中的消息传递(对象消息传递)特性何以调用类和对象中的方法;
对象消息传递是一种动态特性,及接收器和接收器中的方法是在运行程序时确定的;
2)消息传递表达式包含接收器(接收消息的对象/类)和消息,而消息又由选择器和相应的输入参数构成;
3)选择器是一种分段的文本字符串,每个分段以冒号结尾并且后跟参数;
含有一个以上分段的选择器可以拥有空选择器分段(即不带名称的参数);
4)选择器数据类型(SEL)是一种特殊的OC数据类型,它用于在编译源代码时使用具有唯一性的标识符替换选择器;
使用@selector关键字或Foundation框架中的NSSelectorFromString()函数,可以创建类型为SEL的变量;
5)方法签名定义了方法的输入参数和返回值的数据类型;
方法签名不匹配是指编译器无法为对象消息确定适当的方法,或者方法声明与运行时实际执行的方法不匹配;
6)使用动态类型功能可以在运行程序时决定对象的类型,能够由运行时因素决定在程序中使用哪种类型的对象;
OC通过id数据类型支持动态类型;
7)动态绑定指在程序运行时(不是编译时)将消息与方法对应起来的处理过程;
动态绑定实现了OOP的多态性;
可以在不影响已有代码的情况下,将新代码和对象添加到程序中,降低耦合度;
8)使用动态方法决议能够以动态的方式实现方法;
使用OC的@dynamic指令可以告知编译器与某个属性关联的方法会以动态方式实现;
可以使用NSObject实例方法resolveInstanceMethod: 、resolveClassMethod:以动态方式分别实现由选择器指定的实例和类方法;
9)使用动态加载功能可以根据需要加载OC程序的可执行代码和源代码,而无需在启动应用程序时加载它的所有组件;
系统提供的包机制,该机制支持在平台上以动态的方式加载软件;
可以使用NSBundle类管理包
10)Foundation框架提供了很对执行对象内省的方法;
运行程序时,这些API能够以动态的方式查询方法的信息;
他们还可以测试对象的继承性、行为和一致性;
本章介绍了运行时系统的特性和在程序汇总使用的方式;下一章介绍运行时系统的结构和实现方式;
推荐大家读一下《Objecttice-C 2.0运行时系统编程指南》我之前读过,眼下重新读的话,估计能收获会更多些;