RunTime黑科技
1.方法的拦截(hook大法)
(1)可能用到的方法:
a.获取类方法:
Method class_getClassMethod(Class cls, SEL name)
b.获取实例方法:
Method class_getInstanceMethod(Class cos, SEL name)
c.方法实现部分的更换
void method_exchangeImplementations(Method m1, Method m2)
(2)应用场景
我们有时会遇到这么的需求,比如:在页面里使用了UINavigationController嵌套的UIViewController,但页面推过去的时候,由于上一级页面的title太长了,怎么办?我们一般情况之下都会在UIViewController的ViewDidLoad方法里去手动设置它:self.navigationItem.leftBarButtonItem = ...
,这样的做法是可以,但是如果要在之上设置返回item的对应的title的话如何呢,按这种做法我们需要自定义一个CustomView出来,这样才可以设置它的title。但是这里我介绍一个更好的方法可以设置返回item的title:即捕获系统的UIViewController的-(void)setTitle:(NSString *)title
方法:
上代码声明部分:
#import <Foundation/Foundation.h>
@interface SystemMethodHook : NSObject
/**
* 捕获 系统的setTitle方法
*/
+ (void)hookCustomBackTitle;
@end
实现部分:
#import "SystemMethodHook.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@interface UIViewController(Hook)
- (void)setCustomTitle:(NSString *)customTitle;
@end
@implementation UIViewController(Hook)
- (void)setCustomTitle:(NSString *)customTitle
{
//这里判断UIViewController的所要设置的title长度是否大于5
if ([self isKindOfClass:[UIViewController class]] && customTitle.length > 5)
{
UIBarButtonItem * backButtonItem = [[UIBarButtonItem alloc] init];
backButtonItem.title = @"后退";
//backBarButtonItem替换
self.navigationItem.backBarButtonItem = backButtonItem;
}
//未进入判断时,这里其实再次用了hook方法替换(即@selector(setCustomTitle:)->@selector(setTitle:))
[self setCustomTitle:customTitle];
}
@end
@implementation SystemMethodHook
+ (void)hookCustomBackTitle
{
Method m1 = class_getInstanceMethod([UIViewController class], @selector(setTitle:));
Method m2 = class_getInstanceMethod([UIViewController class], @selector(setCustomTitle:));
method_exchangeImplementations(m1, m2);
}
@end
这样我们只需要在设置调用self.title = ...
或- (void)setTitle:(NSString *)title;
方法之前,调用[SystemMethodHook hookCustomBackTitle];
就可以避免返回item的标题太长的情况了。如果要让所有UIViewController都能实现,需要让[SystemMethodHook hookCustomBackTitle]
的调用尽可能靠前,比如放到AppDelegate的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions ;
最开头。
==========
2.分类添加属性
我们都知道OC的分类是不能够添加属性的,因为分类里面不能够生成对应的实例变量。但是OC是有动态特性的语言,它的动态特性就来源于Runtime,所以我们可以利用Runtime对象的关联存储(即set方法将参数值用一个KEY值关联一个对象保存起来,而get方法就在关联的对象中用KEY值去取这个保存的值)来实现分类添加属性的功能。
===============
1.需要用到的方法:
a. void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
参数 object:给哪个对象设置属性
参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
参数 value:给属性设置的值
参数policy:存储策略 (assign 、copy 、 retain就是strong)
b.id objc_getAssociatedObject(id object , const void *key)
利用参数key 将对象object中存储的对应值取出来
===============
2.代码:
声明部分Person+Ext.h:
#import "Person.h"
typedef enum {
EGenderUnknown,
EGenderMale,
EGenderFemale
}EPersonGender;
@interface Person (Ext)
@property (nonatomic, assign) EPersonGender gender;
@end
实现部分Person+Ext.m:
#import "Person+Ext.h"
#import <objc/runtime.h>
@implementation Person (Ext)
char key;
- (void)setGender:(EPersonGender)gender
{
objc_setAssociatedObject(self, &key, @(gender), OBJC_ASSOCIATION_ASSIGN);
}
- (EPersonGender)gender
{
return [objc_getAssociatedObject(self, &key) intValue];
}
@end
这样就在分类中添加了一个属性
3.动态添加方法的实现部分
即在类里不实现方法,外部给出实现(这种方式可能不会在项目里出现)
(1).需要用到的方法
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) ;
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
NSLog(@"Hello %@", string);
});
class_addMethod([Sark class], @selector(sayHello:), myIMP, "v@:@");