Objective-C 的runtime主要是指尽可能的把编译和链接时要执行的逻辑延迟到运行时,它是系统在运行的时候的一些机制,主要是指消息机制。在编译的时候并不决定真正调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错),只有在运行时才会根据函数的名称找到对应的函数来调用。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚至可以交换方法的实现,等等。
在Objective-C中,调用一个方法,最终都会被编译器翻译为:
objc_mesgSend()这样的形式之行。比如下面:
- [obj doSomething]会被编译器转化为:
objc_mesgSend(obj, doSomething)
- 如果消息含有参数,[obj doSomething:str] 则为:
objc_msgSend(obj, @selector(doSomething:), str)
关于runtime在实际开发中到底有什么用,怎么用,我简单总结了下,大概包含以下方面(只是个人理解,如有错误敬请指正):
- 遍历一个类的所有成员变量(属性)\所有方法
- Method Swizzling,交换方法
- Associated Object,关联对象,动态地为某个类添加属性\方法, 修改属性值\方法
下面通过demo简单说明下,以上三点的实现
- 遍历成员变量
项目开发中曾经遇到过这样一个需求:使用UITextField实现的搜索框,产品要求输入文字时候的删除按钮换一种样式。由于UITextField是苹果封装好了的控件,无法通过已提供的属性或api来修改,同时如果只是为了实现这样一个需求而自定义搜索控件,那工作量也是太大了。最后衡量决定可以借助runtime来实现,大概思路如下:
首先遍历UITextField的成员变量,发现删除按钮其实是一个UIButton,找到删除按钮所对应的成员变量名再通过KVC得到该UIButton,然后更改该UIButton的image,最后打完收工。
//记得引入头文件 #import <objc/runtime.h>
unsigned int count = 0;
//遍历成员变量
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSLog(@"%s----%s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
//需要手动释放ivars
free(ivars);
/*
*通过遍历结果获得删除按钮对应的成员变量为_clearButton,通过kvc得到clearButton
*/
UIButton *button = [self.textField valueForKeyPath:@"_clearButton"];
//自定义删除按钮的图片
[button setImage:[UIImage imageNamed:@"menu_item_can"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:@"menu_item_h"] forState:UIControlStateHighlighted];
-
Method Swizzling
这里应用Method Swizzling实现了NSMutableArray的CJAddObject:方法,该方法可以防止NSMutableArray在addObject:时object为nil的crash,请看代码
#import "NSMutableArray+CJNSMutableArray.h"
#import <objc/runtime.h>@implementation NSMutableArray (CJNSMutableArray) //该方法在类或者分类第一次加载内存的时候调用 + (void)load { //NSMutableArray真正的类名应该是__NSArrayM Method orginalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:)); Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(CJAddObject:)); //交换方法 method_exchangeImplementations(orginalMethod, newMethod); } - (void)CJAddObject:(id)anObject { //这里因为已经交换了方法,如果调用addObject:会出现死循环 if (nil != anObject) { [self CJAddObject:anObject]; } } @end
这样通过引入CJNSMutableArray类别,在addObject:nil时也不会crash
NSMutableArray *arry = [NSMutableArray arrayWithCapacity:2];
[arry addObject:@"1"];
[arry addObject:nil];
[arry addObject:@"2"];
NSLog(@"arry = %@",arry);
2016-03-05 22:58:38.549 runtimeTest[2575:161376] arry = (
1,
2
)
- Associated Object
通过类别(categor),我们可以给一个类增加方法,也可以重写已有的方法,但却不能给类增加属性,不过借助Associated Object,则能够实现。下面是给UIButton类别增加linkStr属性的例子。
#import <UIKit/UIKit.h>
@interface UIButton (CJButton)
@property (nonatomic, copy)NSString *linkStr;
@end
在.m文件中
#import "UIButton+CJButton.h"
#import <objc/runtime.h>
@implementation UIButton (CJButton)
static char linkStrKey;
- (void)setLinkStr:(NSString *)linkStr{
//OBJC_ASSOCIATION_COPY_NONATOMIC跟属性声明中的retain、assign、copy是一样
objc_setAssociatedObject(self, &linkStrKey, linkStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)linkStr{
return objc_getAssociatedObject(self, &linkStrKey);
}
@end
这样我们就可以像引用自带属性一样使用linkStr了,
self.button.linkStr = @"https://github.com/";
最后说一下,如果要移除linkStr的关联,可以使用objc_removeAssociatedObjects()函数来移除关联对象。