先来引入一个话题
当项目有一个需求是,要对所有的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。
详细的讨论以下地址 有说明
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; }
总结消息转发:当已声明方法,未实现方式时,可以通过消息转发来处理,避免直接奔溃,消息转发有以上三种方式来处理,如果未实现以上三种转发方式,那就真的奔溃了,亲~~