本文主要記錄了Runtime的消息轉(zhuǎn)發(fā)過程和我們可以利用這個消息轉(zhuǎn)發(fā)機制來做一下事情。
消息
object-c 的消息[receive selector],最終都會變成 objc_msgSend(receive,selector),objc_msgSend只負(fù)責(zé)給消息接受者發(fā)送消息,尋找我們需要調(diào)用的方法。
UIButton *btn = [UIButton new];
[button setBackgroundColor:[UIColor redColor]];
相當(dāng)于
objc_msgSend(button,@selector(setBackgroundColor:), [UIColor redColor]);

IMP
typedef id (*IMP)(id, SEL, ...); 本質(zhì)上就是一個C語言的函數(shù)指針,他只真正能找到我們要執(zhí)行的方法的地址。
Runtime 方法調(diào)用過程
1、判斷target是否為空,selector的方法是否需要執(zhí)行,是否可以執(zhí)行
2、在cache中方法,如果沒有找到
3、就會在方法列表中尋找,一直到NSObject的方法
4、如果找不到,就要進行動態(tài)方法解析
5、消息重定向
這里主要是介紹方法動態(tài)解析和消息轉(zhuǎn)發(fā),就對objc_class和objc_cache IMP 不介紹了。
Runtime 動態(tài)方法解析
+ (BOOL)resolveClassMethod:(SEL)aSel
+ (BOOL)resolveInstanceMethod:(SEL)aSel
#import <Foundation/Foundation.h>
@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
調(diào)用過程
- (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, 但是里面并沒有這個方法的實現(xiàn)。而是通過重寫 resolveInstanceMethod, 通過class_addMethod,來實現(xiàn)動態(tài)添加方法。
如果沒有重寫方法的動態(tài)解析,Runtime就會做方法的重定向,重定向就是將消息重新發(fā)送給另外一個對象。
重定向
如上面的例子,如果我們沒有實現(xiàn)方法的動態(tài)解析,我們可以講發(fā)送給我的消息,轉(zhuǎn)發(fā)給另外一個對象。
*例子
#import <Foundation/Foundation.h>
@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 <Foundation/Foundation.h>
@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 調(diào)用了resolveInstanMethod,但是他根本就沒有實現(xiàn)這個方法。也沒有做方法的動態(tài)解析,而是走了方法的重定向,將這個消息轉(zhuǎn)發(fā)給了Foot的實體myfoot,F(xiàn)oot也沒有聲明resolveInstanMethod,但是他動態(tài)解析了這個方法。
所以上面方法的最終實現(xiàn)這是Foot,消息最開始的target是Person這樣就實現(xiàn)了方法的重定向。
關(guān)聯(lián)屬性
關(guān)聯(lián)屬性,就是用一個關(guān)鍵字,給一個對象附加一個屬性。
方法:
objc_setAssociatedObject()
objc_getAssociatedObject()
例子1:
通過buttonPhoneKey,我們將phoneNUmber和button綁定到了一起,就相當(dāng)于給這個button增加了一個屬性。屬性關(guān)聯(lián)一般的使用場景就是在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:通過屬性關(guān)聯(lián),給UIButton 的even添加block,這樣Button的事件就可以很方便的使用了,特別是很多需要用tag來區(qū)分的事件。
#import <UIKit/UIKit.h>
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