runtime初探究

Objective-C 的runtime主要是指尽可能的把编译和链接时要执行的逻辑延迟到运行时,它是系统在运行的时候的一些机制,主要是指消息机制。在编译的时候并不决定真正调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错),只有在运行时才会根据函数的名称找到对应的函数来调用。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚至可以交换方法的实现,等等。

在Objective-C中,调用一个方法,最终都会被编译器翻译为:
objc_mesgSend()这样的形式之行。比如下面:

  1. [obj doSomething]会被编译器转化为:

objc_mesgSend(obj, doSomething)

  1. 如果消息含有参数,[obj doSomething:str] 则为:

objc_msgSend(obj, @selector(doSomething:), str)


关于runtime在实际开发中到底有什么用,怎么用,我简单总结了下,大概包含以下方面(只是个人理解,如有错误敬请指正):

  • 遍历一个类的所有成员变量(属性)\所有方法
  • Method Swizzling,交换方法
  • Associated Object,关联对象,动态地为某个类添加属性\方法, 修改属性值\方法

下面通过demo简单说明下,以上三点的实现

  1. 遍历成员变量
    项目开发中曾经遇到过这样一个需求:使用UITextField实现的搜索框,产品要求输入文字时候的删除按钮换一种样式。由于UITextField是苹果封装好了的控件,无法通过已提供的属性或api来修改,同时如果只是为了实现这样一个需求而自定义搜索控件,那工作量也是太大了。最后衡量决定可以借助runtime来实现,大概思路如下:

首先遍历UITextField的成员变量,发现删除按钮其实是一个UIButton,找到删除按钮所对应的成员变量名再通过KVC得到该UIButton,然后更改该UIButton的image,最后打完收工。

   //记得引入头文件 #import 

   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];
  1. Method Swizzling
    这里应用Method Swizzling实现了NSMutableArray的CJAddObject:方法,该方法可以防止NSMutableArray在addObject:时object为nil的crash,请看代码
    #import "NSMutableArray+CJNSMutableArray.h"
    #import

    @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
)

  1. Associated Object
    通过类别(categor),我们可以给一个类增加方法,也可以重写已有的方法,但却不能给类增加属性,不过借助Associated Object,则能够实现。下面是给UIButton类别增加linkStr属性的例子。
    #import
    @interface UIButton (CJButton)
    @property (nonatomic, copy)NSString *linkStr;
    @end
    在.m文件中
    #import "UIButton+CJButton.h"
    #import
    @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()函数来移除关联对象。

你可能感兴趣的:(runtime初探究)