前言
运行时API的应用:
- 路由的实现(接口控制app跳任意界面)
- 获取修改对象的成员属性
- 动态添加/交换方法的实现
- 属性关联
- iOS 间接实现多继承的方式:消息转发、类别、delegate和protocol(委托协助主体完成操作任务,将需要定制化的操作预留给委托对象来自定义实现 与block类似) 。
I runtime的作用
因为objective-c是一门动态语言,也就是说只有编译器是不够的,还需要一个运行时系统(runtime system)来执行编译后的代码。
runtime简称运行时,其中最主要的就是消息机制。
对于编译期语言,会在编译的时候决定调用哪个函数。对于OC的函数,是动态调用的,在编译的时候并不能决定真正调用哪个函数,只有在运行时才会根据函数的名称找到对应的函数来调用。
1.1 消息发送的步骤
messages aren’t bound to method implementations until Runtime。(消息直到运行时才会与方法实现进行绑定)
objc_msgSend 是当方法的实现被调用后才会返回数据。
- 当向someObject发送消息时,先在本类中的方法缓存列表中进行查找
- 如果找不到就在本类中的法列表中进行查找
- 如果没找到,就去父类中进行查找。
- 如果没找到,runtime system并不会立即报错使程序崩溃,而是依次执行消息转发。
检查是否有动态添加对应的方法->检查是否有其他对象实现了对应的方法(快速转发给其他对象处理)->(标准消息转发)
- 动态方法解析 :
向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法。
- 快速消息转发: 获取转发对象,
。若该方法返回值对象非nil或非self,那么就进行消息的常规转发。检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法
- 标准消息转发:获取方法签名,
。返回值非空则通过runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名
转发消息,返回值为空则向当前对象发送forwardInvocation:
消息,程序崩溃退出。doesNotRecognizeSelector:
1.2 Runtime API
- 通过对 Runtime 库函数的直接调用
runtime源码:github.com/opensource-…opensource.apple.com/source/objc…其中主要使用的函数定义在message.h和runtime.h这两个文件中。
- 通过 Foundation 框架的 NSObject 类定义的方法
Cocoa 程序中绝大部分类都是 NSObject 类的子类,所以都继承了 NSObject 的行为。(NSProxy 类是个例外,它是个抽象超类)
- class方法返回对象的类;
- isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
- respondsToSelector: 检查对象能否响应指定的消息;
-
检查对象是否实现了指定协议类的方法;conformsToProtocol:
-
返回指定方法实现的地址。methodForSelector:
1.3 理解instance、class object、metaclass的关系
一个实例对象
struct objc_object
的isa指针指向它的
struct objc_class
类对象,类对象的isa指针指向它的元类;
super_class
指针指向了父类的
类对象
,而
元类
的
super_class
指针指向了父类的
元类
。
II 消息转发
2.1 消息处理(动态地添加一个方法实现)
当在相应的类以及父类中找不到类方法实现时会执行
+resolveInstanceMethod:
这。该方法如果在类中不被重写的话,默认返回NO。如果返回NO就表明不做任何处理,走下一步。
如果返回YES的话,就说明在该方法中对这个找不到实现的方法进行了处理。
在
+resolveInstanceMethod:
方法中,我们可以为找不到实现的SEL动态地添加一个方法实现,添加完毕后,就会执行我们添加的方法实现。这样,当一个类调用不存在的方法时,就不会崩溃了。具体做法如下所示:
//运行时方法拦截
- (void)dynamicAddMethod: (NSString *) value {
NSLog(@"OC替换的方法:%@", value);
}
/**
没有找到SEL的IMP实现时会执行下方的方法
@param sel 当前对象调用并且找不到IMP的SEL
@return 找到其他的执行方法,并返回yes
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO; //当返回NO时,会接着执行forwordingTargetForSelector:方法,
[KNRuntimeKit addMethod:[self class] method:sel method:@selector(dynamicAddMethod:)];//动态地添加一个方法实现
return YES;
}
2.2 消息快速转发
如果不对上述消息进行处理的话,也就是+resolveInstanceMethod:返回NO时,会走下一步消息转发,即
-forwardingTargetForSelector:
。
该方法会返回一个类的对象,这个类的对象有SEL对应的实现。当调用这个找不到的方法时,就会被转发到SecondClass中去进行处理。
/**
将当前对象不存在的SEL传给其他存在该SEL的对象
@param aSelector 当前类中不存在的SEL
@return 存在该SEL的对象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self;//当该方法返回self或者nil, 说明不对相应的方法进行转发,那么就进行消息的常规转发。
return [SecondClass new]; //让SecondClass中相应的SEL去执行该方法
例子: 教师执行手术
//Returns the object to which unrecognized messages should first be directed.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
Doctor *doctor = [[Doctor alloc]init];
if ([doctor respondsToSelector:aSelector]) {
return doctor;
}
return nil;
}
这个方式的好处是隐藏了我要转发的消息,没有类目那么清晰。
2.3 消息常规转发
如果不将消息转发给其他类的对象,那么就只能自己进行处理了。
如果上述方法返回nil或者self的话,会执行
-methodSignatureForSelector:
方法来获取方法的参数以及返回数据类型(方法签名)。
返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送
doesNotRecognizeSelector:
消息,程序崩溃,报出找不到相应的方法实现的崩溃信息。
//在+resolveInstanceMethod:返回NO时就会执行下方的方法,下方也是将该方法转发给SecondClass。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
//查找父类的方法签名
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if(signature == nil) {
// signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
signature = [someObj methodSignatureForSelector:aSelector]; //返回指定方法签名
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SecondClass * forwardClass = [SecondClass new];
SEL sel = invocation.selector;
if ([forwardClass respondsToSelector:sel]) {
[invocation invokeWithTarget:forwardClass];
} else {
[self
2.4 两种消息转发方式的比较
- 快速消息转发:简单、快速、但仅能转发给一个对象。
- 标准消息转发:稍复杂、较慢、但转发操作实现可控,可以实现多对象转发
III 编译器指令 @encode()
苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发。
ocrtTypeEncodings
NSValue 的
+valueWithBytes:objCType:
第二个参数需要用
@encode()
指令来创建一种内部表示的字符串 。
@encode(int) → i
+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;
+ (NSValue *)value:(const void *)value withObjCType:(const char
3.1 Objective-C type encodings
Code | Meaning |
c | A char |
i | An int |
s | A short |
l | A long ,l is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
void)testtypeEncodings{
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));//指针的标准编码是加一个前置的 ^
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));//char * 拥有自己的编码 *,因为 C 的字符串被认为是一个实体,而不是指针。
NSLog(@"BOOL : %s", @encode(BOOL));//BOOL 是 c,而不是i。原因是 char 比 int 小。BOOL 更确切地说是 signed char 。
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));
float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));
typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));
}
结果:
2022-06-14 11:18:06.069425+0800 SDKSample[9300:3648699] int : i
2022-06-14 11:18:06.069517+0800 SDKSample[9300:3648699] float : f
2022-06-14 11:18:06.069559+0800 SDKSample[9300:3648699] float * : ^f
2022-06-14 11:18:06.069597+0800 SDKSample[9300:3648699] char : c
2022-06-14 11:18:06.069634+0800 SDKSample[9300:3648699] char * : *
2022-06-14 11:18:06.069669+0800 SDKSample[9300:3648699] BOOL : B
2022-06-14 11:18:06.069706+0800 SDKSample[9300:3648699] void : v
2022-06-14 11:18:06.070191+0800 SDKSample[9300:3648699] void * : ^v
2022-06-14 11:18:06.070230+0800 SDKSample[9300:3648699] NSObject * : @
2022-06-14 11:18:06.070267+0800 SDKSample[9300:3648699] NSObject : {NSObject=#}
2022-06-14 11:18:06.070303+0800 SDKSample[9300:3648699] [NSObject] : #
2022-06-14 11:18:06.070339+0800 SDKSample[9300:3648699] NSError ** : ^@
2022-06-14 11:18:06.070374+0800 SDKSample[9300:3648699] int[] : [5i]
2022-06-14 11:18:06.070870+0800 SDKSample[9300:3648699] float[] : [3f]
2022-06-14 11:18:06.072298+0800 SDKSample[9300:3648699] struct : {_struct=sqQ}
3.2 method encodings
Code | Meaning |
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
带inout 的参数表明它在发消息时对象即可传入又可传出,将参数特别标注为 in 或 out,程序将避免一些来回的开销。
增加一个 bycopy 修饰符以保证发送了一份完整的拷贝
IV 运行时API应用
- 路由的实现(接口控制app跳任意界面)
- 获取修改对象的成员属性
- 动态添加/交换方法的实现
- 属性关联
- iOS 间接实现多继承的方式:消息转发、类别、delegate和protocol(委托协助主体完成操作任务,将需要定制化的操作预留给委托对象来自定义实现 与block类似) 。
- NSClassFromString、NSSelectorFromString 使用例子
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#import "KNRuntimeKit.h"
@implementation KNRuntimeKit
/**
获取类名
@param class 相应类
@return NSString:类名
*/
+ (NSString *)fetchClassName:(Class)class {
const char *className = class_getName(class);
return [NSString stringWithUTF8String:className];
}
/**
获取成员变量
@param class Class
@return NSArray
*/
+ (NSArray *)fetchIvarList:(Class)class {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
const char *ivarName = ivar_getName(ivarList[i]);
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
dic[@"type"] = [NSString stringWithUTF8String: ivarType];
dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
[mutableList addObject:dic];
}
free(ivarList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取类的属性列表, 包括私有和公有属性,以及定义在延展中的属性
@param class Class
@return 属性列表数组
*/
+ (NSArray *)fetchPropertyList:(Class)class {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
const char *propertyName = property_getName(propertyList[i]);
[mutableList addObject:[NSString stringWithUTF8String: propertyName]];
}
free(propertyList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取类的实例方法列表:getter, setter, 对象方法等。但不能获取类方法
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchMethodList:(Class)class {
unsigned int count = 0;
Method *methodList = class_copyMethodList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取协议列表
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchProtocolList:(Class)class {
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String: protocolName]];
}
return [NSArray arrayWithArray:mutableList];
return nil;
}
/**
往类上添加新的方法与其实现
@param class 相应的类
@param methodSel 方法的名
@param methodSelImpl 对应方法实现的方法名
*/
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl {
Method method = class_getInstanceMethod(class, methodSelImpl);
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(class, methodSel, methodIMP, types);
}
/**
方法交换
@param class 交换方法所在的类
@param method1 方法1
@param method2 方法2
*/
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
Method firstMethod = class_getInstanceMethod(class, method1);
Method secondMethod = class_getInstanceMethod(class, method2);
method_exchangeImplementations(firstMethod, secondMethod);
}
@end