ios runtime IMP指针 消息转发机制
2016-05-13 08:47
531 查看
本文代码是根据消息转发机制来写的, 有不妥之处, 请大神指正
1. UIViewController (ViewDidLoadName)文件 UIViewController的category
在实现viewDidLoad系统方法的前提下 添加自定义的方法
2. Person类有一个run的方法(没有实现),这里展示了OC中的消息转发机制, 使其不崩溃并实现方法,或者转到Car的run方法来实现
直接上代码(注释很全, 简单易懂)
ViewController.m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// ViewController.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "ViewController.h"
#import "UIViewController+ViewDidLoadName.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建Person对象 执行run方法, 但是Person类中并没有实现run方法
// 我们利用消息转发机制防止其崩溃,或者用其他方法来代替[per run]的方法
Person *per = [[Person alloc] init];
[per run];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end</span>
UIViewController (ViewDidLoadName).h文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// UIViewController+ViewDidLoadName.h
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController (ViewDidLoadName)
@end</span>
UIViewController (ViewDidLoadName).m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// UIViewController+ViewDidLoadName.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "UIViewController+ViewDidLoadName.h"
#import <objc/runtime.h>
// 有返回值的IMP
typedef id (* _IMP) (id, SEL, ...);
// 没有返回值的IMP(定义为VIMP)
typedef void (* _VIMP) (id, SEL, ...);
@implementation UIViewController (ViewDidLoadName)
+(void)load
{
// 保证交换方法只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 获取原始方法
Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
// 获取原始方法的实现指针(IMP)
_VIMP viewDidLoad_IMP = (_VIMP)method_getImplementation(viewDidLoad);
// 重新设置方法的实现
method_setImplementation(viewDidLoad, imp_implementationWithBlock(^(id target, SEL action) {
// 调用系统的原生方法
viewDidLoad_IMP(target, @selector(viewDidLoad));
// 新增的功能代码
NSLog(@"%@ did load", target);
}));
});
}
//+ (void)load
//{
// // 保证交换方法只执行一次
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// // 获取到这个类的viewDidLoad方法, 它的类型是一个objc_method结构体的指针
// Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
//
// // 获取到自己刚刚创建的一个方法
// Method myViewDidLoad = class_getInstanceMethod(self, @selector(myViewDidLoad));
//
// // 交换两个方法的实现
// method_exchangeImplementations(viewDidLoad, myViewDidLoad);
//
//
//
// });
//}
//
//- (void)myViewDidLoad
//{
// // 调用系统的方法
// [self myViewDidLoad];
// NSLog(@"%@ did load", self);
//}
@end</span>
Person.h 文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Person.h
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
@end</span>
Person.m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Person.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"
@implementation Person
/**
* 首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。
方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
*/
void run (id self, SEL _cmd)
{
// 程序会走我们C语言的部分
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
/**
* 方案一
*
* 为Person类动态增加了run方法的实现
由于没有实现run对应的方法, 那么系统会调用resolveInstanceMethod让你去做一些其他操作
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// if(sel == @selector(run)) {
// class_addMethod([self class], sel, (IMP)run, "v@:");
// return YES;
// }
return [super respondsToSelector:sel];
}
/** 方案二
* 现在不对方案一做任何的处理, 直接调用父类的方法
系统会走到forwardingTargetForSelector方法
*/
//- (id)forwardingTargetForSelector:(SEL)aSelector
//{
// return [[Car alloc] init];
//}
/**
* 不实现forwardingTargetForSelector,
系统就会调用方案三的两个方法
methodSignatureForSelector 和 forwardInvocation
*/
/**
* 方案三
开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。
所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
*/
/**
* methodSignatureForSelector
* 用来生成方法签名, 这个签名就是给forwardInvocation中参数NSInvocation调用的
*
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *sel = NSStringFromSelector(aSelector);
// 判断你要转发的SEL
if ([sel isEqualToString:@"run"]) {
// 为你的转发方法手动生成签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
/**
* 关于生成签名类型"v@:"解释一下, 每个方法会默认隐藏两个参数, self, _cmd
self 代表方法调用者, _cmd 代表这个方法SEL, 签名类型就是用来描述这个方法的返回值, 参数的,
v代表返回值为void, @表示self, :表示_cmd
*/
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
// 新建需要转发消息的对象
Car *car = [[Car alloc] init];
if ([car respondsToSelector:selector]) {
// 唤醒这个方法
[anInvocation invokeWithTarget:car];
}
}
@end</span>
Car .m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Car.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "Car.h"
@implementation Car
- (void)run
{
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
@end</span>
1. UIViewController (ViewDidLoadName)文件 UIViewController的category
在实现viewDidLoad系统方法的前提下 添加自定义的方法
2. Person类有一个run的方法(没有实现),这里展示了OC中的消息转发机制, 使其不崩溃并实现方法,或者转到Car的run方法来实现
直接上代码(注释很全, 简单易懂)
ViewController.m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// ViewController.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "ViewController.h"
#import "UIViewController+ViewDidLoadName.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建Person对象 执行run方法, 但是Person类中并没有实现run方法
// 我们利用消息转发机制防止其崩溃,或者用其他方法来代替[per run]的方法
Person *per = [[Person alloc] init];
[per run];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end</span>
UIViewController (ViewDidLoadName).h文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// UIViewController+ViewDidLoadName.h
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController (ViewDidLoadName)
@end</span>
UIViewController (ViewDidLoadName).m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// UIViewController+ViewDidLoadName.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "UIViewController+ViewDidLoadName.h"
#import <objc/runtime.h>
// 有返回值的IMP
typedef id (* _IMP) (id, SEL, ...);
// 没有返回值的IMP(定义为VIMP)
typedef void (* _VIMP) (id, SEL, ...);
@implementation UIViewController (ViewDidLoadName)
+(void)load
{
// 保证交换方法只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 获取原始方法
Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
// 获取原始方法的实现指针(IMP)
_VIMP viewDidLoad_IMP = (_VIMP)method_getImplementation(viewDidLoad);
// 重新设置方法的实现
method_setImplementation(viewDidLoad, imp_implementationWithBlock(^(id target, SEL action) {
// 调用系统的原生方法
viewDidLoad_IMP(target, @selector(viewDidLoad));
// 新增的功能代码
NSLog(@"%@ did load", target);
}));
});
}
//+ (void)load
//{
// // 保证交换方法只执行一次
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// // 获取到这个类的viewDidLoad方法, 它的类型是一个objc_method结构体的指针
// Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
//
// // 获取到自己刚刚创建的一个方法
// Method myViewDidLoad = class_getInstanceMethod(self, @selector(myViewDidLoad));
//
// // 交换两个方法的实现
// method_exchangeImplementations(viewDidLoad, myViewDidLoad);
//
//
//
// });
//}
//
//- (void)myViewDidLoad
//{
// // 调用系统的方法
// [self myViewDidLoad];
// NSLog(@"%@ did load", self);
//}
@end</span>
Person.h 文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Person.h
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
@end</span>
Person.m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Person.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"
@implementation Person
/**
* 首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。
方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
*/
void run (id self, SEL _cmd)
{
// 程序会走我们C语言的部分
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
/**
* 方案一
*
* 为Person类动态增加了run方法的实现
由于没有实现run对应的方法, 那么系统会调用resolveInstanceMethod让你去做一些其他操作
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// if(sel == @selector(run)) {
// class_addMethod([self class], sel, (IMP)run, "v@:");
// return YES;
// }
return [super respondsToSelector:sel];
}
/** 方案二
* 现在不对方案一做任何的处理, 直接调用父类的方法
系统会走到forwardingTargetForSelector方法
*/
//- (id)forwardingTargetForSelector:(SEL)aSelector
//{
// return [[Car alloc] init];
//}
/**
* 不实现forwardingTargetForSelector,
系统就会调用方案三的两个方法
methodSignatureForSelector 和 forwardInvocation
*/
/**
* 方案三
开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。
所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
*/
/**
* methodSignatureForSelector
* 用来生成方法签名, 这个签名就是给forwardInvocation中参数NSInvocation调用的
*
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *sel = NSStringFromSelector(aSelector);
// 判断你要转发的SEL
if ([sel isEqualToString:@"run"]) {
// 为你的转发方法手动生成签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
/**
* 关于生成签名类型"v@:"解释一下, 每个方法会默认隐藏两个参数, self, _cmd
self 代表方法调用者, _cmd 代表这个方法SEL, 签名类型就是用来描述这个方法的返回值, 参数的,
v代表返回值为void, @表示self, :表示_cmd
*/
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
// 新建需要转发消息的对象
Car *car = [[Car alloc] init];
if ([car respondsToSelector:selector]) {
// 唤醒这个方法
[anInvocation invokeWithTarget:car];
}
}
@end</span>
Car .m文件
[objc]
view plain
copy
print?
<span style="font-size:18px;">// Car.m
// MethodSwizzlingIMPdemo
//
// Created by 帝炎魔 on 16/5/12.
// Copyright © 2016年 帝炎魔. All rights reserved.
//
#import "Car.h"
@implementation Car
- (void)run
{
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
@end</span>
相关文章推荐
- iOS runtime的理解
- iOS 打包 测试 发布
- IOS数据存储之CoreData使用优缺点
- 学习Coding-iOS开源项目日志(四)
- protobuf---真正-- 在IOS中的使用
- iOS 获取手机的型号,系统版本,设备名称等
- Access restriction: The type 'AudioStream' is not
- [10秒学会] - iOS NSAttributedString 首行缩进
- iOS基础开发-自定义控件
- iOS开发-----KVC与KVO的区别
- iOS基础开发-字典转模型(模型思想)
- iOS基础开发-懒加载
- iOS开发设置textFiled的placeholder颜色
- iOS基础开发-九宫格显示数据页面
- iOS内存管理(ARC,MRC)
- ios自定义控件——星星评分控件
- iOS学习——self和super关键字的解析
- iOS学习- 01 程序模版
- iOS中KVC和KVO的区别
- iOS开发中的错误整理,关于用绑定Tag取控件的注意事项,有时候不绑定也是个错!