前端外刊评论 2017-10-13
文章分享至我的个人技术博客:cainrun.github.io/15065147177…
前面我们把RunTime的一些基本知识都了解了一遍, 知道了在Objective-C的方法调用是属于消息传送的机制.
接着呢, 我们知道了每个类都有一个isa的结构体指针, 在这个结构体里, 我们得到指定类的所有属性, 所有方法的列表, 也可以知道这个所属的父类是什么等等的.
这只是RunTime黑魔法的一丢丢应用, 如果没有看过之前那些文章的朋友可以去这里看看:
转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.
在之前的文章里, 我们就有接触过RunTime的消息机制, 通过Clang把Object.m文件转成Object.mm文件, 然后就可以看得到里面的所有东西, 包括是怎么调用方法的也可以明确的看到.
这次我们换一个方式来实现, 首先我们声明一个类, 内部实现两个小方法:
#import "RunTimeMessageModel.h"
@implementation RunTimeMessageModel
- (void)cl_post {
NSLog(@"被调用了: %@, 当前对象为: %@", NSStringFromClass([self class]), self);
}
- (void)cl_getWithCount:(NSInteger)count {
NSLog(@"被%ld人调用了", count);
}
@end在这里我们还需要修改一点东西, 不然我们没法用RunTime的消息机制:
搞定完一切之后, 我们就来实现一下:
#import "RunTimeMessageController.h"
#import "RunTimeMessageModel.h"
#import <objc/message.h>
@interface RunTimeMessageController ()
@end
@implementation RunTimeMessageController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = NSStringFromClass([self class]);
Class getClass = objc_getClass("RunTimeMessageModel");
NSLog(@"Get The Class is: %@", getClass);
// Xcode 会自动屏蔽通过objc_msgSend创建对象, 我们可以去到工程里设置
// Build Setting -> Enable Strict Checking of objc_msgSend Calls 改成No就好了.
RunTimeMessageModel *messageModel = objc_msgSend(getClass, @selector(alloc));
NSLog(@"alloc Object: %@", messageModel);
// 在不调用init方法, 我们也可以通过发消息调用想用的方法, 这里调用没有在.h文件里声明的方法会警告该方法没有声明
objc_msgSend(messageModel, @selector(cl_post));
messageModel = objc_msgSend(messageModel, @selector(init));
NSLog(@"init Object: %@", messageModel);
objc_msgSend(messageModel, @selector(cl_post));
// 还有另外一种写法, 就是把所有东西都集合在一起, 也就是我们常用的[[NSObject alloc] init];的原型
RunTimeMessageModel *messageModelTwo = objc_msgSend(objc_msgSend(objc_getClass("RunTimeMessageModel"), @selector(alloc)), @selector(init));
objc_msgSend(messageModelTwo, @selector(cl_getWithCount:), 5);
}
@end打印结果:
2017-09-27 22:55:44.713329+0800 RunTimeExample[74324:4732411] -[RunTimeMessageController viewDidLoad] 第27行 Get The Class is: RunTimeMessageModel 2017-09-27 22:55:44.714726+0800 RunTimeExample[74324:4732411] -[RunTimeMessageController viewDidLoad] 第33行 alloc Object: <RunTimeMessageModel: 0x60400001b9f0> 2017-09-27 22:55:44.715881+0800 RunTimeExample[74324:4732411] -[RunTimeMessageModel cl_post] 第15行 被调用了: RunTimeMessageModel, 当前对象为: <RunTimeMessageModel: 0x60400001b9f0> 2017-09-27 22:55:44.716663+0800 RunTimeExample[74324:4732411] -[RunTimeMessageController viewDidLoad] 第40行 init Object: <RunTimeMessageModel: 0x60400001b9f0> 2017-09-27 22:55:44.718265+0800 RunTimeExample[74324:4732411] -[RunTimeMessageModel cl_post] 第15行 被调用了: RunTimeMessageModel, 当前对象为: <RunTimeMessageModel: 0x60400001b9f0> 2017-09-27 22:55:44.719543+0800 RunTimeExample[74324:4732411] -[RunTimeMessageModel cl_getWithCount:] 第20行 被5人调用了
由于之前的文章里已经有做过解释了, 这里就不详细讲解了.
在这里还有比较有意思的用处, 就是交换两个方法, 这里另外建一个类:
// RunTimeMethodModel.h文件
#import <Foundation/Foundation.h>
@interface RunTimeMethodModel : NSObject
@property (nonatomic, copy) NSString *cl_height;
@property (nonatomic, copy) NSString *cl_weight;
- (NSString *)cl_height;
- (NSString *)cl_weight;
@end
// RunTimeMethodModel.m文件
#import "RunTimeMethodModel.h"
@implementation RunTimeMethodModel
- (NSString *)cl_height {
return @"我身高180";
}
- (NSString *)cl_weight {
return @"我体重280";
}
@end- (void)viewDidLoad {
[super viewDidLoad];
self.title = NSStringFromClass([self class]);
RunTimeMethodModel *methodModel = [[RunTimeMethodModel alloc] init];
NSLog(@"身高: %@", methodModel.cl_height);
NSLog(@"体重: %@", methodModel.cl_weight);
Method methodOne = class_getInstanceMethod([methodModel class], @selector(cl_height));
Method methodTwo = class_getInstanceMethod([methodModel class], @selector(cl_weight));
method_exchangeImplementations(methodOne, methodTwo);
NSLog(@"打印的内容: %@", [methodModel cl_height]);
}打印结果:
2017-09-28 00:36:20.277653+0800 RunTimeExample[76955:4827313] -[RunTimeMethodController viewDidLoad] 第27行 身高: 我身高180 2017-09-28 00:36:20.279144+0800 RunTimeExample[76955:4827313] -[RunTimeMethodController viewDidLoad] 第28行 体重: 我体重280 2017-09-28 00:36:20.283090+0800 RunTimeExample[76955:4827313] -[RunTimeMethodController viewDidLoad] 第35行 打印的内容: 我体重280
PS: 但这里需要注意一点, 由于这里的ViewController会销毁, 但method_exchangeImplementations会一直存在, 再次进来的时候, 就会再次根据上次交换过的顺序再次交换.
那怎么办呢? 查了一下资料, 发现有两个解决的方案:
我们可以把交换方法的步骤放在+load, 试试看:
+ (void)load {
Method methodOne = class_getInstanceMethod(self, @selector(cl_height));
Method methodTwo = class_getInstanceMethod(self, @selector(cl_weight));
method_exchangeImplementations(methodOne, methodTwo);
}
- (NSString *)cl_height {
return @"我身高180";
}
- (NSString *)cl_weight {
return @"我体重280";
}打印结果:
2017-09-30 20:32:48.054168+0800 RunTimeExample[81266:5241395] -[RunTimeMethodController viewDidLoad] 第23行 身高: 我体重280 2017-09-30 20:32:48.054436+0800 RunTimeExample[81266:5241395] -[RunTimeMethodController viewDidLoad] 第24行 体重: 我身高180 2017-09-30 20:33:19.179724+0800 RunTimeExample[81266:5241395] -[RunTimeMethodController viewDidLoad] 第23行 身高: 我体重280 2017-09-30 20:33:19.179947+0800 RunTimeExample[81266:5241395] -[RunTimeMethodController viewDidLoad] 第24行 体重: 我身高180
PS: 虽然在+load这个方法里的确是可以保证方法交换只有一次, 但这里有一个弊端, 就是当程序一运行就会执行这个方法交换了, 这并不是一个好的方案.
这里我们尝试第二个方案, 使用+initialize方法:
+ (void)initialize {
Method methodOne = class_getInstanceMethod(self, @selector(cl_height));
Method methodTwo = class_getInstanceMethod(self, @selector(cl_weight));
method_exchangeImplementations(methodOne, methodTwo);
}
- (NSString *)cl_height {
return @"我身高180";
}
- (NSString *)cl_weight {
return @"我体重280";
}打印结果:
2017-09-30 20:42:49.750880+0800 RunTimeExample[81385:5249133] -[RunTimeMethodController viewDidLoad] 第23行 身高: 我体重280 2017-09-30 20:42:49.752335+0800 RunTimeExample[81385:5249133] -[RunTimeMethodController viewDidLoad] 第24行 体重: 我身高180
ok, 这满足了我们的需要了, 这解释一下+load和+initialize的区别:
当然并不是说在+load上用是不对的, 也不是说+initialize就一定是对的, 根据场景的需要来使用才是王道.
从刚刚我们就知道, 可以使用method_exchangeImplementations交换两个方法, 但只应用在本类, 现在我们来看看别的应用:
@implementation BaseModel
- (void)cl_logBaseModel {
NSLog(@"Base Model Log");
}
@end@implementation InterceptModel
- (void)cl_logInterceptModel {
NSLog(@"Intercept You Method ");
}
@end最终的实现:
+ (void)initialize {
Method mehtodOne = class_getInstanceMethod([BaseModel class], @selector(cl_logBaseModel));
Method mehtodTwo = class_getInstanceMethod([InterceptModel class], @selector(cl_logInterceptModel));
method_exchangeImplementations(mehtodOne, mehtodTwo);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
BaseModel *baseModel = [[BaseModel alloc] init];
[baseModel cl_logBaseModel];
}打印结果:
2017-10-01 12:36:03.488764+0800 RunTimeExample[82538:5345309] -[InterceptModel cl_logInterceptModel] 第15行 Intercept You Method
发现方法是被InterceptModel这个类拦截, 并且替换了InterceptModel的方法.
这里我们都是用实例方法来作为例子, 那是不是说只能使用实例方法呢?
其实并不是的, 类方法也可以交换和拦截:
#import "BaseModel.h"
@implementation BaseModel
- (void)cl_logBaseModel {
NSLog(@"Base Model Log");
}
+ (void)cl_logBaseModelClass {
NSLog(@"Base Model Class Log");
}
@end@implementation InterceptModel
- (void)cl_logInterceptModel {
NSLog(@"Intercept You Method ");
}
+ (void)cl_logInterceptModelClass {
NSLog(@"Intercept Class You Method ");
}
@end最终实现:
+ (void)initialize {
// 拦截实例方法
Method mehtodOne = class_getInstanceMethod([BaseModel class], @selector(cl_logBaseModel));
Method mehtodTwo = class_getInstanceMethod([InterceptModel class], @selector(cl_logInterceptModel));
method_exchangeImplementations(mehtodOne, mehtodTwo);
// 拦截类方法
Method classMehtodOne = class_getClassMethod([BaseModel class], @selector(cl_logBaseModelClass));
Method classMehtodTwo = class_getClassMethod([InterceptModel class], @selector(cl_logInterceptModelClass));
method_exchangeImplementations(classMehtodOne, classMehtodTwo);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
BaseModel *baseModel = [[BaseModel alloc] init];
[baseModel cl_logBaseModel];
[BaseModel cl_logBaseModelClass];
}2017-10-01 12:59:13.229912+0800 RunTimeExample[82996:5374475] -[InterceptModel cl_logInterceptModel] 第15行 Intercept You Method 2017-10-01 12:59:13.230480+0800 RunTimeExample[82996:5374475] +[InterceptModel cl_logInterceptModelClass] 第20行 Intercept Class You Method
项目地址: github.com/CainRun/iOS…
微信
支付宝 hive运行在hadoop基础上。选择一个hadoop服务器、安装hadoop。connect jdbc:hive2://<host>:<port>/<db>;auth=noSasl root 123