Runtime乃iOS装逼必备,接下来将会详细的说下Runtime。
一、Runtime简介
二、类与对象基础数据结构
1.class类型
class类型表示为:
typedef struct object_class *Class
object_class 结构体为:
struct object_class{
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list *methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
}OBJC2_UNAVAILABLE;
2.objc_object
objc_object是表示一个类的实例的结构体
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
isa指针指向其类
3.元类(Meta Class)
所有的类自身也是一个对象,meta-class是一个类对象的类,meta-class中存储着一个类的所有类方法。
任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
4.Category
Category结构体
typedef struct objc_category *Category
struct objc_category{
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
三、runtime关联对象
1.设置关联值
参数说明:
object:与谁关联,通常是传self
key:唯一键,在获取值时通过该键获取,通常是使用static
const void *来声明
value:关联所设置的值
policy:内存管理策略,比如使用copy
void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)
2.获取关联值
参数说明:
object:与谁关联,通常是传self,在设置关联时所指定的与哪个对象关联的那个对象
key:唯一键,在设置关联时所指定的键
id objc_getAssociatedObject(id object, const void *key)
3.取消关联
void objc_removeAssociatedObjects(id object)
Runtime关联对象的简单应用:
场景:为UIButton增加一个Category,定义一个方法,使用block去实现button的点击回调
UIButton+Addition.h
#import
#import
// 声明一个button点击事件的回调block
typedef void(^ButtonClickCallBack)(UIButton *button);
@interface UIButton (Addition)
// 为UIButton增加的回调方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;
@end
UIButton+Addition.m
#import "UIButton+Addition.h"
// 声明一个静态的索引key,用于获取被关联对象的值
static char *buttonClickKey;
@implementation UIButton (Addition)
- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
// 将button的实例与回调的block通过索引key进行关联:
objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 设置button执行的方法
[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClicked {
// 通过静态的索引key,获取被关联对象(这里就是回调的block)
ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);
if (callBack) {
callBack(self);
}
}
@end
四、方法与消息
1.SEL
SEL是表示一个方法的selector的指针。
定义: typedef struct objc_selector *SEL 在runtime的源码内没有找到具体的objc_selector定义。
通过下面三种方法可以获取SEL:
a、sel_registerName函数
b、Objective-C编译器提供的@selector()
c、NSSelectorFromString()方法
2、IMP
IMP是一个函数指针,指向方法实现的地址。
定义:
id (*IMP)(id, SEL,...)
第一个参数:是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
第二个参数:是方法选择器(selector)
接下来的参数:方法的参数列表。
SEL就是为了查找方法的最终实现IMP的
3、Method
Method用于表示类定义中的方法
typedef struct objc_method *Method
struct objc_method{
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
4、方法调用流程
[receiver message] 转化为
objc_msgSend(receiver, selector) 或者
objc_msgSend(receiver, selector, arg1, arg2,...)
当消息发送给一个对象时首先从运行时系统缓存使用过的方法中寻找。
如果找到,执行该方法,如未找到继续执行下面的步骤
objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。
如果没有找到selector,objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。
依此,会一直沿着类的继承体系到达NSObject类。
5、消息转发
当我们不能确定一个对象是否能接收某个消息时,会先调用respondsToSelector:来判断一下:
if([self respondsToSelector:@selector(method)]){
[self performSelector:@selector(method)];
}
当一个对象无法接收某一消息时,就会启动所谓“消息转发(message forwarding)”机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,通过控制台,我们可以看到异常信息。这段异常信息实际上是由NSObject的“doesNotRecognizeSelector”方法抛出的。
消息转发机制基本上分为三个步骤:
a、动态方法解析
对象在接收到未知的消息时,首先会调用所属类的类方法
+resolveInstanceMethod:(实例方法)或者
+resolveClassMethod:(类方法)。
在这个方法中,我们有机会为该未知消息新增一个“处理方法”,通过运行时class_addMethod函数动态添加到类里面就可以了。
这种方案更多的是为了实现@dynamic属性。//@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。
b、备用接收者
如果在上一步无法处理消息,则Runtime会继续调以下方法
- (id)forwardingTargetForSelector:(SEL)aSelector
返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。
测试代码
//转发目标类
@interface NoneClass : NSObject
@end
@implementation NoneClass
+(void)load
{
NSLog(@"NoneClass _cmd: %@", NSStringFromSelector(_cmd));
}
- (void) noneClassMethod
{
NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));
}
@end
@implementation MyTestObject
//…
//将消息转出某对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));
NoneClass *none = [[NoneClass alloc] init];
if ([none respondsToSelector: aSelector]) {
return none;
}
return [super forwardingTargetForSelector: aSelector];
}
//…
@end
c、完整转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
首先要通过,指定方法签名,若返回nil,则表示不处理。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"testInstanceMethod"]){
return [NSMethodSignature signatureWithObjcTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
若返回方法签名,则会进入下一步调用以下方法。
//消息重定向,修改实现方法,修改响应对象等。
- (void)forwardInvovation:(NSInvocation)anInvocation
{
[anInvocation invokeWithTarget:_helper];
[anInvocation setSelector:@selector(run)];
[anInvocation invokeWithTarget:self];
}
d、抛出异常
作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。
- (void)doesNotRecognizeSelector:(SEL)aSelector
虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。
五、Method Swizzling
利用 method_exchangeImplementations 来交换2个方法中的IMP
利用 class_replaceMethod 来修改类
利用 method_setImplementation 来直接设置某个方法的IMP
都是偷换了selector的IMP
实例
//NSArray
#import "NSArray+Swizzle.h"
@implementation NSArray (Swizzle)
- (id)myLastObject
{
id ret = [self myLastObject];
NSLog(@"********** myLastObject *********** ");
return ret;
}
@end
//调换IMP
#import
#import "NSArray+Swizzle.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
method_exchangeImplementations(ori_Method, my_Method);
NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);
return 0;
}
}