本博客主要分以下几个方面来介绍iOS中的runtime
- Runtime的概念介绍
- iOS中的消息机制
- Runtime的作用
Objective-C语言中的Runtime概念
- 动态编程语言和静态编程语言的区别
- 动态编程语言:在程序运行过程中可以改变数据类型的结构,对象的函数,变量可以被修改删除。例如OC
- 静态编程语言:在程序编译阶段检查数据的类型,数据类型的结构不可以在运行时被改变。例如C
- Runtime---运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件,在/usr/include/objc,这些函数支持用纯C的函数来实现和Objective-C同样的功能
- Runtime---是开源的,目前苹果公司和GNU各自维护一个开源的runtime版本,apple open runtime;
- Runtime---在OC中的使用方式
- 通过Objective-C源代码
- 通过类NSObject的方法
class 返回对象的类
isKindeOfClass:和isMemberOfClass: 检查对象是否在指定的类继承体系中
respondsToSelector: 检查对象能否响应指定的消息
conformsToProtocol: 检查对象是否实现了指定协议类的方法
methodForSelector: 返回指定方法实现的地址
- 通过运行时系统的函数
通过导入头文件"#import<objc/message.h>"
调用相关函数
iOS中的消息机制
- Objective-C中消息机制的相关概念
- message(消息) --包括了函数名+参数列表的一种抽象
- method(方法)-- 是真正的存在的代码。如:- (int)meaning { return 42; }
- selector(方法选择器)--通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作
- SEL(方法选择器) -- 是一个char*指针,仅仅表示它所代表的方法名字,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度
//SEL
SEL selector = @selector(message); //@selector不是函数调用,只是给这个坑爹的编译器的一个提示
NSLog (@"%s", (char *)selector); //print message
//以下函数命名方式这被认为是一种编译错误
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
- IMP --函数指针
- 我们可以通过SEL方便、快速、准确的获得它所对应的IMP(也就是函数指针),而在取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数传递到其他的方法,或者实例变量里面,从而获得极大的动态性
Runtime的作用
-
关联对象:主要为分类增加属性和实例变量。
能用扩展一般不用继承,因为随着继承深度的增加,代码可维护性变差
static char myKey; //定义一个静态字符 /*! * 给关联对象赋值 * * @param self 需要添加关联的分类 * @param key const void *,key仅仅是一个地址,不是字符串内容。具体指向内容不用关心 * @param value 关联对象的值 * @param policy 关联策略,类似于对象的属性修饰符,OBJC_ASSOCIATION_RETAIN_NONATOMIC等 * */ objc_setAssociatedObject(self, &myKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //给关联对象赋值 /*! * 根据关联的key,获取关联的值 * * @param self 需要添加关联的分类 * @param myKey onst void *,key仅仅是一个地址,不是字符串内容。具体指向内容不用关心 * * @return 关联的对象的值 */ objc_getAssociatedObject(self, &myKey);
获取对象的私有变量,私有方法,动态添加属性、方法等。
- 获取类名
```objectivec
Class class = [objc class];
class_getName(class);
class_getSuperclass(class);
```
- 获取成员变量(可以获取私有成员变量)
CaculatorMaker *make = [[CaculatorMaker alloc] init];
Class objcClass = [make class];
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(objcClass, &outCount);
for (int i = 0; i<outCount; i++) {
Ivar ivar = ivars[i];
const char *ivarString = ivar_getName(ivar);
NSLog(@"the variable of make is %s",ivarString);
}
free(ivars);
- 获取属性
/*!
* 获取对象的所有属性和属性值
*
* @param object 对象
*
* @return 属性和属性值数组
*/
- (NSMutableArray *)getObjectPropertyAndValues:(id)object
{
NSMutableArray *array = [NSMutableArray new];
Class objcClass = [object class];
unsigned int outCount = 0 ;
objc_property_t *properties = class_copyPropertyList(objcClass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *propertyKey = [NSString stringWithUTF8String:propertyName];
NSString *propertyValue = [object valueForKey:propertyKey];
RACTuple *tuple = RACTuplePack(propertyKey,propertyValue);
[array addObject:tuple];
}
return array;
}
- 动态添加方法
CaculatorMaker *make = [[CaculatorMaker alloc] init];
/*!
* runtime添加方法
*
* @param class 被添加方法的类
* @param addMethod: 方法的名称
* @param imp: 实现这个方法的函数
* @param type: 定义函数返回值类型和参数类型的字符串[Type Encodings](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
* @return 添加成功或者失败
*/
class_addMethod([make class], @selector(addMethod:), (IMP)addMethod, "i@:@");
[make performSelector:@selector(addMethod:) withObject:@"new Method"];
//添加的方法的具体实现
int addMethod(id self, SEL _cmd, NSString *str)
{
NSLog(@"new method is %@",str);
return 100;
}
- 动态添加属性和成员变量均未成功,属性添加完成之后的setter方法和getter方法不会实现。
- 消息转发(message forwarding)-当一个对象无法接收某一消息时,就会启动消息转发机制。
通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃。
- 消息转发的步骤:1.动态方法解析 2.备用接收者 3. 完整转发
- 动态方法解析:当对象接收到未实现的方法时,为自动调用resolveInstanceMethod:和resolveClassMethod:
//当对象接收到未知方法时,动态添加以下方法。作为默认执行,防止程序崩溃
void functionForMethod1(id self, SEL _cmd)
{
NSLog(@"当未实现某个方法时默认执行此函数");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
return [super resolveClassMethod:sel];
}
```
+ 备用接收者:如果没有用动态方法解析处理消息,则Runtime会继续调以下方法:
```objectivec
//如果对象实现了这个方法,并返回一个非nil的值,则这个对象会作为消息的接收者。且消息会被分发到这个对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
id standByObj;
//....standByObj 备用接收对象的初始化等操作
return standByObj;
return [super forwardingTargetForSelector:aSelector];
}
- 完整转发:如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了
/*!
* 消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名
*
* @param aSelector 需要转发的方法
*
* @return 新的方法签名
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
/*!
* 将消息转发给其它对象
*
* @param anInvocation 需要转发的消息的selector,目标(target)和参数
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
- 方法交换(Method Swizzling)
- 给view controller的viewWillAppear:中添加跟踪代码,将viewWillAppear:与自定义的dg_viewWillAppear:交换,代码如下:
#import "UIViewController+Tracking.h"
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
/*!
* 在load中实现方法交换,
*/
+ (void)load
{
//
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(dg_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//本类是否已添加swizzledMethod,未添加则进行添加
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)dg_viewWillAppear:(BOOL)animated
{
[self dg_viewWillAppear:animated];
NSLog(@"viewWilleAppear");
}
@end