86540698 2019-07-01
我们看下苹果官方文档对runtime的定义
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the shared library found at /usr/lib/libobjc.A.dylib.译文如下
Objective-C运行时是一个运行时库,它提供对Objective-C语言的动态属性的支持,因此被所有Objective-C应用程序链接。 Objective-C运行时库支持函数在/usr/lib/libobjc.A.dylib中的共享库中实现。在Objective-C中,消息直到运行时才绑定到方法实现。编译器将把方法调用转化为消息发送
例如如下代码
[receiver message]
将会被转化为这种调用方式
objc_msgSend(receiver, selector)
在消息需要绑定参数的时候会转化如下
objc_msgSend(receiver, selector, arg1, arg2, ...)
那么抓花为发送消息之后都做了什么呢?
[receiver message]
这里我们发现还缺少了一种情况,那就是递归在父类的methodlist里面也没有找到对应的实现,这个时候就会报错 unrecognized selector send to instance X
Runtime 为这种可能提供了最后的机会,就是触发消息转发流程
Show Me The Code:
动态添加方法:
#import "AViewController.h" #import <objc/runtime.h> @interface AViewController () @end @implementation AViewController - (void)viewDidLoad { [super viewDidLoad]; [self performSelector:@selector(speak)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(speak)) { class_addMethod([self class], sel, (IMP)fakeSpeak, "v@:"); // 关于最后一个参数可以看https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html return true; } return [super resolveInstanceMethod:sel]; } void fakeSpeak(id target, SEL _cmd){ NSLog(@"method added"); } @end
快速转发
#import "AViewController.h" #import <objc/runtime.h> @interface AViewController () @end @implementation AViewController - (void)viewDidLoad { [super viewDidLoad]; [self performSelector:@selector(speak)]; } - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(speak)) { return [XXXX new]; } return nil; } @end
完整转发
#import "AViewController.h" #import <objc/runtime.h> @interface AViewController () @end @implementation AViewController - (void)viewDidLoad { [super viewDidLoad]; [self performSelector:@selector(speak)]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(speak)) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { [anInvocation setSelector:@selector(otherMethod)]; [anInvocation invokeWithTarget:self]; } - (void)otherMethod{ NSLog(@"%s",__func__); } @end
对消息转发的流程有了一些基本概念以后我们就可以稍微深入看看方法交换这个理念了。
有的时候我们可能会面对一些需求,比如在每个页面中统一都做的一些处理,像访问埋点等逻辑,如果一个一个去改写的话十分麻烦,用继承的方式去做慢慢会产生各种耦合的情况,这里,我们可以使用方法交换的方式去统一添加处理。
比如我们需要在每一个 ViewController viewDidLoad 的方法中输出一个log
先创建一个 category
#import "UIViewController+Log.h" #import <objc/runtime.h> @implementation UIViewController (Log) static void AGExchangeMethod(Class cls, SEL originSelector, SEL newSelector) { Method originMethod = class_getInstanceMethod(cls, originSelector); Method newMethod = class_getInstanceMethod(cls, newSelector); // method_exchangeImplementations(newMethod, originMethod); BOOL addMethod = class_addMethod(cls, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); if (addMethod) { class_replaceMethod(cls, newSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); }else { method_exchangeImplementations(newMethod, originMethod); } } + (void)load { static dispatch_once_t once; dispatch_once(&once, ^{ AGExchangeMethod([self class], @selector(viewDidLoad), @selector(Logging)); }); } - (void)Logging{ NSLog(@"%s",__func__); [self Logging]; } @end
编译运行,你可以看到控制台会输出 Logging
这里有几个地方需要特别留意下
load
中, 该方法会在类被加载的时候执行比如我们想要为 UIViewController 添加一个flag属性记录状态,但是无法更改 UIViewController,那么我们可以在 category 中添加属性
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface UIViewController (Log) @property (nonatomic ,copy) NSString *flag; @end NS_ASSUME_NONNULL_END
然后在其他的 viewController 中使用
- (void)viewDidLoad { [super viewDidLoad]; self.flag = @"active"; }
运行后可以看到崩溃 unrecognized selector sent to instance
,
这是因为在 category 中 property修饰符并不会自动为我们生成成员变量,而我们知道,属性其实是 ivar + getter & setter ,所以我们可以使用 runtime 来手动关联:
在 category 的 .m 文件中增加以下代码
- (void)setFlag:(NSString *)flag { objc_setAssociatedObject(self, @selector(flag), flag, OBJC_ASSOCIATION_COPY); } - (NSString *)flag { return objc_getAssociatedObject(self, _cmd); }
然后就可以在其他 viewController 中随意使用了,由于 objc_setAssociatedObject 也是在ARC管理之下的所以我们也不必手动释放。
虽然 Runtime 有诸多魔幻的使用方法,但是不建议过多的使用(除非掌握的很熟练),除非是开发框架,否则多个互相交换的方法和动态的属性在调试的时候会很无奈的。。。
hive运行在hadoop基础上。选择一个hadoop服务器、安装hadoop。connect jdbc:hive2://<host>:<port>/<db>;auth=noSasl root 123