开发进阶 Runtime首页投稿(暂停使用,暂停投稿)手机移动程序开发

iOS开发--runtime运行时概述和使用

2016-10-08  本文已影响297人  断风刀

运行时是oc运行原理中的一个难点,我们有时候在弄懂后也不太明白怎么去使用。所以下面内容试着分析runtime原理,然后再提供一些demo 。所以前面部分介绍运行时机制的一些原理,后面会穿插代码,介绍运行时机制的使用。

runtime和OC的三种交互方式

runtime 是什么东西,runtime翻译过来就是运行时,是OC语言的一个特性。简单概括就是OC语言尽可能地把数据类型的确定从编译时和链接时移到了运行时。OC中的类在创建和运行的过程中,会调用到父类NSObject的方法,并且通过消息发送来调用方法,在这个过程中,我们可以获取到NSObject的方法属性,或者截取类调用方法时的消息转发,从而来修改类中的某些方法和属性,这意味着我们可以通过运行时修改系统本身控件的方法和属性。
OC具备运行时特性就意味着 Objective-C 语言不仅需要一个编译器,同时也需要一个运行时系统来执行编译好的代码。这儿的运行时系统扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。这个系统是通过三种方式来和源代码发生关系。这三种方式分别为Objective-C通过 Objective-C 源代码;通过 Foundation 框架中类NSObject 的方法;通过直接调用运行时系统的函数。当我们写好运行时的代码后,运行时系统就会去执行我们实现代码。下面是这三种交互方式介绍

第一种交互方式:当您编译Objective-C类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数。这些数据结构包含类定义和协议类定义中的信息,运行时系统的主要功能就是根据源代码中的表达式发送消息。比如我们可以利用 NSObject 类中的 methodForSelector:方法,您可以获得一个指向方法实现的指针,并可以使用该指针直接调用方法进行实现。

void (*setter)(id, SEL, BOOL);

int i;

setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];

第二种交互方式 Cocoa程序中绝大部分类都是NSObject类的子类,所以大部分都继承了NSObject类的方法,因此继承了NSObject的行为。所以我们可以通过NSObject类的类方法来使用运行时。

例如,NSObject 类定义了 description 方法,该方法用于返回该类内容的字符串表示。这主要是用来调试程序——GDB 中的 print-object 方法就是直接打印出该方法返回的字符串。NSObject 的子类可以重新实现该方法以提供更多的信息。例如,NSArray 类改写了该方法来返回 NSArray 类包含的每个对象的内容。
某些 NSObject 的方法只是简单地从运行时系统中获得信息,从而允许对象进行一定程度的自我检查。例如,class 返回对象的类;isKindOfClass:和 isMemberOfClass:则检查对象是否在指定的类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol:检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。

NSString*method=@"test";
SELselector=NSSelectorFromString(method);
[personperformSelector:selector];

第三种交互方式:通过消息转发。运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件在/usr/include/objc中。这些函数支持用纯C的函数来实现和Objective-C同样的功能。还有一些函数构成了 NSObject 类方法的基础。这些函数使得访问运行时系统接口和提供开发工具成为可能。尽管大部分情况下它们在 Objective-C 程序不是必须的,但是有时候对于 Objecitve-C 程序来说某些函数是非常有用的。我们在实际开发中用得最多的也是这个交互方式。下面的代码就是使用到了这种交互方式。

Objective-C消息可以通过 invokeWithTarget:方法来转发消息,当对象没有相应的方法实现而无法相应某消息时,通过消息转发将参数和消息转发给对象处理,转发消息后的返回值将返回给原来的消息转发者。

Objective-C

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

if ([someOtherObject respondsToSelector:

[anInvocation selector]])

[anInvocation invokeWithTarget:someOtherObject];

else

[super forwardInvocation:anInvocation];

}

运行时的使用

runtime有很多用处,但在我们实际开发中可能用到的作用是“修改原生控件的属性和方法”,或者是“热修复”(热修复是jspatch 已经封装好了运行时接口)。具体什么意思,就是如果我们发现系统提供的原生控件原有的方法和功能不能满足我们的开发需求的时候,我们就可以通过runtime去修改原生控件的方法和属性,或者干脆为原生控件添加方法和属性。当然,更神奇的一种用法是,当你发现xcode的原生控件存在bug,而你又刚好要用到使用到出bug的功能。你就可以自定义一个方法,替换掉xcode原来的出bug的方法,这样子你就可以正常使用该控件了。在热修复中也会用到runtime方法,如果上线的app某个方法出现bug,考虑到苹果上线审核的繁琐流程,我们可以使用runtime 中的方法交换,自定义一个方法,用来替换出现Bug的方法,这样子就能把上线应用中的bug进行修复。
下面是介绍runtime最常用的几种用法,其中第一种是需要改变系统原生控件的例子,当我们点击textFiled后,placeholderLabel文字会变白色,而不会变消失。这就需要通过runtime 去修改textFiled的placeholderLabel的属性。

1.png

上面的做法要怎么实现,我们先来看下让runtime如此牛逼的几个方法。要使用这几个方法,你得先导入一个头文件。
--#include <objc/runtime.h>

导入头文件后,在头文件中我们可以找到下面这些方法,通过下面的方法名我们大概都能猜出方法的意思。
用来为类添加方法
class_addMethod
用来为类替换方法
class_replaceMethod
用来为类替换属性
class_replaceProperty
用来注册一个类
objc_registerClassPair
用来创建一个类
objc_allocateClassPair
用来获取对象isa指针指向的对象的属性
objc_getClasses
更多的方法在头文件中都可以找到,点击进去头文件中就能看到对应的方法还有说明。

下面先看下怎么使用上面提供的方法,来修改textFiled的placeHold属性。

<1>程序启动的时候加载

+(void)load

{//需要交换的方法

Method setPlaceholderMethod = class_getInstanceMethod(self,@selector(setPlaceholder:)) ;

//自定义的方法

Method setZJWPlaceholderMethod = class_getInstanceMethod(self,@selector(set_Placeholder:));

//方法交换,我调用系统的 setPlaceholder 方法也相当于调用了set_Placeholder的方法

method_exchangeImplementations(setPlaceholderMethod, setZJWPlaceholderMethod);

}

<2>自定义方法的实现

-(void)seting_Placeholder:(UIColor*)placeholder{

//设置占位符

[selfseting_Placeholder:placeholder];

//设置占位文字颜色

[selfsetPlaceholderColor:self.placeholderColor];

}

<3>设置占位文字颜色

- (void)setPlaceholderColor:(UIColor*)placeholderColor

{

// 把占位文字颜色先保存

objc_setAssociatedObject(self,@"placeholderColor",placeholderColor,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

// 等真正设置占位文字的时候,在去设置占位文字颜色,获取占位文字控件

UILabel *placeholderLabel = [self valueForKey:@"placeholderLabel"];

//拿到控件去设置颜色

placeholderLabel.textColor= placeholderColor;

}

<4>返回已添加属性placeholderColor,占位文字颜色

- (UIColor*)placeholderColor

{

return objc_getAssociatedObject(self,@"placeholderColor");

}

@end

2.在对应设定UITextField状态的文件中调用方法实现(通过target方式)
-(void)awakeFromNib

{

//设置光标的颜色

self.tintColor= [UIColor blueColor];

//监听文本框开始编辑1.代理,2.target ,3.通知

[self addTarget:self action:@selector(textBegin)forControlEvents:UIControlEventEditingDidBegin];

//结束监听

[self addTarget:self action:@selector(textEnd) for ControlEvents:UIControlEventEditingDidEnd];

}
//结束编辑时占位文字的颜色

-(void)textEnd{

self.placeholderColor= [UIColor lightGrayColor];

}

//设置开始编辑时占位文字的颜色

-(void)textBegin{

self.placeholderColor= [UIColor blueColor];

}

@end

上面代码是实现修改控件的属性。下面代码是使用运行时修改控件本身的方法。使用运行时替换系统控件本身的方法。
下面是一个简单的demo

//使用系统原生代码替换系统的原生方法
-(void)loadView
{
 //创建出一个新的方法
  Method otherMethod = class_getClassMethod([UIImage class], @selector(imageNameNextWith:));
//获取系统本身的方法
  Method method = class_getClassMethod([UIImage class], @selector(imageNamed:));
//用新建的方法替换系统本身的方法
  method_exchangeImplementations(otherMethod, method);
}

//实现新创建的方法
+(UIImage *)imageNameNextWith:(NSString *)nameString{
 
  UIImage *image = nil;
  image = [self imageNameNextWith:nameString];
  return image;
 
}
//下面实际上就是使用我们已经替换过的方法了
- (IBAction)addImage:(id)sender {
 
  //添加一个图片到界面上
  UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(20, 20, 100, 100)];
 
  imageView.image = [UIImage imageNamed:@"icon_os7"];
}

runtime还可以用来动态创建类,动态创建类主要作用是解耦,提升程序的性能。下面我们动态创建一个类,并给类添加方法。当我们需要使用到该类的时候,该类才会创建。

- (void)viewDidLoad {
  [super viewDidLoad];
 
  //创建一个名为xjCustomView的类,他是UIView的子类
  Class newClass = objc_allocateClassPair([UIView class], "XjCustomView", 0);
  //为该类增加一个名为report的方法
  class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
 
  //注册该类
  objc_registerClassPair(newClass);
 
  //创建一个xjCustomView的实例
  id instanceOfNewClass = [[newClass alloc]init];
  //用实例调用report方法
  [instanceOfNewClass performSelector:@selector(report)];
  
}

//新建一个ReportFunction的方法
void ReportFunction(id self,SEL _cmd)
{
  NSLog(@"This objct is %p.",self);
  NSLog(@"Class is %@, and super is %@.",[self class],[self superclass]);

  Class currentClass = [self class];

  for (int i = 0; i<5; i++) {

    NSLog(@"Follow the isa pointer %d times gives %p",i,currentClass);
    currentClass = object_getClass(currentClass);
  }
  NSLog(@"NSObject`s class is %p",[NSObject class]);
  NSLog(@"NSObject`s meta class is %p", object_getClass([NSObject class]));
 
}

上面介绍的是runtime最常用的几种用法,也是最基本的用法,如果掌握了上面几种,你理解起来runtime的其他用法都不会有太多的问题。下面提供一个demo,如果有兴趣,或者实际开发中有这种需求,看了demo中的代码,都能满足你的开发需求。

上一篇下一篇

猜你喜欢

热点阅读