Objective-C的Method Swizzle、对象模型、消息机制、消息转发的详解
先来引入一个话题当项目有一个需求是,要对所有的UIViewController的viewWillApear:animte方法进行监听,而项目很大,.m的控制器文件很多,而且该项目已经开发好了,对这个方法监听不可能进入到控制器里一个一个的添加此时Objective-C有一个运行时的方法特别好的解决这种问题,当然该方法不是说只能解决上述这种情况比如,做统计,需要对系统的库的某个方法
先来引入一个话题
当项目有一个需求是,要对所有的UIViewController的viewWillApear:animte方法进行监听,而项目很大,.m的控制器文件很多,而且该项目已经开发好了,对这个方法监听不可能进入到控制器里一个一个的添加
此时Objective-C有一个运行时的方法特别好的解决这种问题,当然该方法不是说只能解决上述这种情况
比如,做统计,需要对系统的库的某个方法或多个方法进行监听,添加一些自定义的功能在此方法里,这就会用到运行时的Method swizzle
先来了解一下Objective-C语言的发展
本链接详细介绍了Objective-C语言
http://baike.baidu.com/link?url=3Jqn-qSTvdupQz8aCRcjBFuzy7eznY4Ko_fUgKtD6D6MCSfRR7m61wZz4yAkm5XP0heedN1rrC8YduCTdnl41_#reference-[1]-459423-wrap
Objective-C是扩充C的面向对象的编程语言,它是一门动态语言,它是使用C写成一套非常强大的运行时库,采用了smalltalk语言的消息机制,而且编译OC语言的编译器是LLVM(Low Level Virtual Machine 底层虚拟机),所有OC可以兼容C/C++语法
Objective-C
优点
1.完美支持C/C++
2.消息机制(采用smalltalk)
3.强大的运行时系统
4.所有的对象推迟到运行期间才定义类型
5.内存管理的方式(引用计数)
缺点
1.由于是动态语言,很多异常从运行期间抛出来,不是很容易的定位错误位置
2.没有GC(垃圾收集器),所以需要程序员理解内存管理,避免引起内存的循环引用,导致内存泄露
以下讲一个NSArray的案例,把lastObject与自定义的myLastObject方法交换,当项目中使用lastObject方法时,就相当于使用myLastObject方法,并且lastObject方法本身的功能还是存在的
代码:
#import <Foundation/Foundation.h>
@interface NSArray (Swizzle)
@end
@implementation NSArray (Swizzle)
- (id)myLastObject
{
id ret = [self myLastObject];
NSLog(@" 这是替换后的方法被打印了 *********** myLastObject ");
return ret;
}
@end
使用
#import <Foundation/Foundation.h>
#import <objc/objc.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1.这个替换操作只需要执行一遍
//原始NSArray方法 lastObject
Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
//替换后的方法 myLastObject
Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
//执行替换操作
method_exchangeImplementations(ori_Method, my_Method);
//2.以后使用NSArray的时候调用 lastObject方法就相当于执行myLastObject
//当再次使用NSArray的时候,就相当于执行了mylastObject方法
NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"最终打印 : %@",string);
NSArray * arr = [array copy];
NSLog(@"arr最后 一个是:%@", [arr lastObject]);
}
return 0;
}
这是打印结果
疑问:[self myLastObject]; 这不就造成递归了吗?
原理:当把lastObject和myLastObject交换,实际交换的是方法的IMP(后面会讲到),当我们执行[self lastObject];时,实际上就是执行了lastObject(本身的作用)和myLastObject里注入的新内容,如下图:
method_exchangeImplementations(method *,method*)方法方法就是调换IMP
下面在举一个例子
代码:
#import <UIKit/UIKit.h>
@interface UIViewController (Swizzle)
@end
#import <objc/message.h>
@implementation UIViewController (Swizzle)
+ (void)load
{
NSString *strClass = NSStringFromClass(self.class);
NSLog(@"strClass=%@", strClass);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalMethod = @selector(viewWillAppear:);
SEL destinateMethod = @selector(zr_viewWillAppear:);
Method originMethod = class_getInstanceMethod(class, originalMethod);
Method destMethod = class_getInstanceMethod(class, destinateMethod);
IMP imp = method_getImplementation(destMethod);
const char * conchar = method_getTypeEncoding(destMethod);
BOOL swizzleMethod = class_addMethod(class, originalMethod, imp, conchar);
if(swizzleMethod){
class_replaceMethod(class, destinateMethod, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
} else {
method_exchangeImplementations(originMethod, destMethod);
}
});
}
- (void)zr_viewWillAppear:(BOOL)animte
{
[self zr_viewWillAppear:animte];
NSLog(@"**** 替换后的方法 *****viewWillAppear:%@", self);
}
@end
执行
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *view = [[UIViewController alloc] init];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
@end
结果是:
和上面的解释一样,读者自己领悟
这么好用的Method Swizzle有什么好处和坏处了
使用 Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全。
Method swizzling 可以帮助我们写出更好的,更高效的,易维护的代码。但是如果滥用它,也将会导致难以排查的bug。
讨论
这里是一些 Method Swizzling的陷阱:- Method swizzling is not atomic
- Changes behavior of un-owned code
- Possible naming conflicts
- Swizzling changes the method's arguments
- The order of swizzles matters
- Difficult to understand (looks recursive)
- Difficult to debug
详细的讨论以下地址 有说明
http://blog.csdn.net/yiyaaixuexi/article/details/9374411
以下说说Objective-C的对象模型
我们都知道OC有强大的运行时系统,那么OC的对象模型是怎么一会事儿了?
C语言没有类,但是有结构体(是个程序员都知道)
OC的很多库都是直接或者间接集成子NSObject类(即是协议也是类)
一个类,可以有多个属性,可以有多个方法(含类方法和实例方法)
OC的每个对象都有一个isa指针, 简单的意思isa指针指向这个类,
id是万能类型,它的运行时类型是objc_object
Class的运行时类型是objc_class
它们都是结构体
以下是一个类的构成部分在运行时
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
} OBJC2_UNAVAILABLE;
我们可以在runtime.h头文件中看到这个
isa 每个类都有一个isa指针
super_class 父类是谁
instance_size 本类的实例大小
objc_ivar_list 本类的所有属性在这个列表中
objc_method_list 本类所有的方法在这个列表中
objc_cache 本类缓存
objc_protocal_list 本类所有协议列表
所以对于一个OC的类在运行时系统时是在objc_class这个结构体中很清楚的描述着
下面说说消息机制
学过其他的语言的同学都知道,除了Objective-C语言调用方法是发送消息机制外,基本上主流的语言都是通过实例调用方法
举个例子:
C#调用一个方法
object.console();
OC调用一个方法
[object console];
写法上有小点儿区别,原理可大不相同
C#是直接调用实例对象方法
OC是发送一个消息,一个消息是一个SEL类型,方法名就是一个字符串,编译时发送消息会转换成objc_msgSend方法
这个方法需提供两个参数,接收对象和消息名称
objc_msgSend(receiver, selector)
所有整个发送消息的过程都在这个方法里进行了
前面说过,每个对象都有一个isa指针代表着所属的类,每个类都有方法列表和属性列表,当接收到消息是,系统首先会根据isa指针查找相应的类,然后在这个类的方法列表去寻找对应的方法(这个方法可能在本类也可能在父类),如果找到了,那就会正常执行,如果没有找到,就会抛出异常
下面说说消息转发
当Objective-C发送消息时,在runtime去当前类和父类中去寻找对应的方法,找不到时,不会马上崩溃,而是可以做消息转发处理,如果没有做消息转发处理,那就会奔溃
有三种方式处理消息转发
第一种:
+ (BOOL)resolveInstanceMethod:(SEL)sel; 实例方法的消息转发
+ (BOOL)resolveClassMethod:(SEL)sel; 类方法的消息转发第二种:
- (id)forwardingTargetForSelector:(SEL)aSelector;
第三种
先生成方法签名,在通过forwardInvocation来实现消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation第一种:
+ (BOOL)resolveInstanceMethod:(SEL)sel; 实例方法的消息转发
+ (BOOL)resolveClassMethod:(SEL)sel; 类方法的消息转发
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Person : NSObject
- (void)run;
@end
@implementation Person
void run(id self, SEL _cmd){
NSLog(@"%@ %s", self, sel_getName(_cmd));
}
//第一种:该方法是是实例方法的转发
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
//在这里给run方法添加一个实现
if(sel == @selector(run)){
//v@:意思是 v表示void无返回值,@表示self, :表示参数_cmd
class_addMethod(self, sel, (IMP)run, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1.实例方法run被声明了,但是没有实现
//被调用,会抛出异常, 若实现了消息转发,则会调用消息转发定义的方法
Person *p = [[Person alloc] init];
[p run];
}
return 0;
}
<span style="font-size:18px;"></span>
<span style="font-size:18px;"></span>
第二种消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector;
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Car : NSObject
@end
@implementation Car
- (void)run
{
NSLog(@"通过Person类的消息转发,调用Car类的方法");
}
@end
@interface Person : NSObject
- (void)run;
@end
@implementation Person
//第二种,消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(run)){
return [[Car alloc] init];
} else {
return nil;
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
//第二种消息转发方式
//快速转发路径
Person *p = [[Person alloc] init];
[p run];
}
return 0;
}
第三种消息转发
先生成方法签名,在通过forwardInvocation来实现消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Car : NSObject
- (void)run;
@end
@implementation Car
- (void)run
{
NSLog(@"Car类中的run方法,第三种方法通过签名实现转发");
}
@end
@interface Person : NSObject
- (void)run;
@end
@implementation Person
//第三种,用来生成方法签名,然后这个签名就是给forwardInvocation中的参数NSInvocation调用的,用来转发消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if([NSStringFromSelector(aSelector) isEqualToString:@"run"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
Car *car = [[Car alloc] init];
if([car respondsToSelector:selector]){
[anInvocation invokeWithTarget:car];
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
//第三种, 先生成一个方法签名,然后通过forwardInvocation中的参数来实现转发消息
Person *p = [[Person alloc] init];
[p run];
}
return 0;
}
总结消息转发:当已声明方法,未实现方式时,可以通过消息转发来处理,避免直接奔溃,消息转发有以上三种方式来处理,如果未实现以上三种转发方式,那就真的奔溃了,亲~~
更多推荐
所有评论(0)