本文主要记录了Runtime的消息转发过程和我们可以利用这个消息转发机制来做一下事情。
消息
object-c 的消息[receive selector],最终都会变成 objc_msgSend(receive,selector),objc_msgSend只负责给消息接受者发送消息,寻找我们需要调用的方法。
UIButton *btn = [UIButton new];
[button setBackgroundColor:[UIColor redColor]];
相当于
objc_msgSend(button,@selector(setBackgroundColor:), [UIColor redColor]);
IMP
typedef id (*IMP)(id, SEL, ...); 本质上就是一个C语言的函数指针,他只真正能找到我们要执行的方法的地址。
Runtime 方法调用过程
1、判断target是否为空,selector的方法是否需要执行,是否可以执行
2、在cache中方法,如果没有找到
3、就会在方法列表中寻找,一直到NSObject的方法
4、如果找不到,就要进行动态方法解析
5、消息重定向
这里主要是介绍方法动态解析和消息转发,就对objc_class和objc_cache IMP 不介绍了。
Runtime 动态方法解析
+ (BOOL)resolveClassMethod:(SEL)aSel
+ (BOOL)resolveInstanceMethod:(SEL)aSel
#import
@interface Foot : NSObject
- (void)resolveInstanMethod;
@end
#import "Foot.h"
#import "Objc/runtime.h"
@implementation Foot
void footCanBeEat(id self, SEL _cmd)
{
NSLog(@"foot can be eat");
}
+ (BOOL)resolveInstanceMethod:(SEL)aSel
{
if (aSel == @selector(resolveInstanMethod)) {
class_addMethod([self class], @selector(resolveInstanMethod), (IMP)footCanBeEat, "v@:");
}
return NO;
}
@end
调用过程
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Foot *YY = [[Foot alloc]init];
[YY resolveInstanMethod];
}
Foot 想外声明了一个方法,resolveInstanMethod, 但是里面并没有这个方法的实现。而是通过重写 resolveInstanceMethod, 通过class_addMethod,来实现动态添加方法。
如果没有重写方法的动态解析,Runtime就会做方法的重定向,重定向就是将消息重新发送给另外一个对象。
重定向
如上面的例子,如果我们没有实现方法的动态解析,我们可以讲发送给我的消息,转发给另外一个对象。
*例子
#import
@interface Foot : NSObject
@end
void footCanBeEat(id self, SEL _cmd)
{
NSLog(@"foot can be eat");
}
+ (BOOL)resolveInstanceMethod:(SEL)aSel
{
if (aSel == @selector(resolveInstanMethod)) {
class_addMethod([self class], @selector(resolveInstanMethod), (IMP)footCanBeEat, "v@:");
}
return NO;
}
@end
#import
@interface Person : NSObject
- (void)resolveInstanMethod;
@end
*********************************************
#import "Person.h"
#import "Foot.h"
#import "Objc/runtime.h"
@interface Person ()
@property (nonatomic, strong) Foot *myFoot;
@end
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
_myFoot = [[Foot alloc]init];
}
return self;
}
-(id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(resolveInstanMethod)) {
return _myFoot;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
*****************************************************
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *YY = [[Person alloc]init];
[YY resolveInstanMethod];
}
从这里看见,person的事例YY 调用了resolveInstanMethod,但是他根本就没有实现这个方法。也没有做方法的动态解析,而是走了方法的重定向,将这个消息转发给了Foot的实体myfoot,Foot也没有声明resolveInstanMethod,但是他动态解析了这个方法。
所以上面方法的最终实现这是Foot,消息最开始的target是Person这样就实现了方法的重定向。
关联属性
关联属性,就是用一个关键字,给一个对象附加一个属性。
方法:
objc_setAssociatedObject()
objc_getAssociatedObject()
例子1:
通过buttonPhoneKey,我们将phoneNUmber和button绑定到了一起,就相当于给这个button增加了一个属性。属性关联一般的使用场景就是在category给一个类增加一个属性。
- (void)viewDidload{
static const void *buttonPhoneKey = "phoneNum";
NSString *phoneNumber = @"18126504453";
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
[button setTitle:@"associate" forState:UIControlStateNormal];
[button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
objc_setAssociatedObject(button, buttonPhoneKey, phoneNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.view addSubview:button];
}
- (void)buttonAction:(UIButton *)btn
{
NSString *phoneNum = objc_getAssociatedObject(btn, buttonPhoneKey);
NSLog(@"phone num = %@ ", phoneNum);
}
例子2:通过属性关联,给UIButton 的even添加block,这样Button的事件就可以很方便的使用了,特别是很多需要用tag来区分的事件。
#import
typedef void(^tapAction)(UIButton *sender) ;
@interface UIButton (Block)
- (void)controlEvent:(UIControlEvents )event withBlock:(tapAction)block;
@end
#import "UIButton+Block.h"
#import "objc/runtime.h"
static const void *buttonAssociateKey = &buttonAssociateKey;
@implementation UIButton (Block)
- (void)controlEvent:(UIControlEvents )event withBlock:(tapAction)block
{
objc_setAssociatedObject(self, buttonAssociateKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(buttonAction:) forControlEvents:event];
}
- (void)buttonAction:(UIButton *)btn
{
tapAction block = objc_getAssociatedObject(btn, buttonAssociateKey);
if (block) {
block(btn);
}
}
@end