总结iOS中runtime的使用

来源:互联网 发布:手写字体生成器软件 编辑:程序博客网 时间:2024/05/18 20:06

一、runtime简介

RunTime简称运行时。OC就是运行时机制,也就是在运行时候 的一些机制,其中最主要的是消息机制。

对于C语言,函数的调用在编译的时候会决定调用哪个函数

对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称

找到对应的函数来调用。

事实证明:

    在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。

    在编译阶段,C语言调用未实现的函数就会报错。

二、runtime作用

1.发送消息

方法调用的本质,就是让对象发送消息。

objc_msgSend,只有对象才能发送消息,因此以objc开头.

使用消息机制前提,必须导入#import <objc/message.h>

消息机制简单使用

消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

// 创建person对象

Person *p = [[Person alloc] init];
 
// 调用对象方法

[p eat];

// SEL:方法编号,根据方法编号就可以找到对应方法实现

[p performSelector:@selector(eat)];
 
// 本质:让对象发送消息

objc_msgSend(p, @selector(eat));
 
// 调用类方法的方式:两种

// 第一种通过类名调用本质类名转换成类对象
[Person eat];

// 第二种通过类对象调用

[[Personclass] eat];
 
[personClass performSelector:@selector(eat)];

// 用类名调用类方法,底层会自动把类名转换成类对象调用

// 本质:让类对象发送消息

objc_msgSend([Personclass], @selector(eat));

2.交换方法

开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。

方式一:继承系统的类,重写方法.

方式二:使用runtime,交换方法.

@implementation ViewController
 
 
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  // 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
  // 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (UIImage *)xmg_imageNamed:(NSString *)imageName;
  // 步骤二:交换imageNamed和xmg_imageNamed的实现,就能调用xmg_imageNamed,间接调用xmg_imageNamed的实现。
  UIImage *image = [UIImage imageNamed:@"123"];
 
     imageNamed:
   实现方法:底层调用PH_imageNamed
 
   本质:交换两个方法的实现imageNamed和PH_imageNamed方法
   调用imageNamed其实就是调用PH_imageNamed
 
 
   imageNamed加载图片,并不知道图片是否加载成功
   以后调用imageNamed的时候,就知道图片是否加载
 
 
}
 
@end
 
 
@implementation UIImage (Image)

// 加载分类到内存的时候调用
+ (void)load
{
  // 交换方法实现,方法都是定义在类里面
  // class_getMethodImplementation:获取方法实现
  // class_getInstanceMethod:获取对象
  // class_getClassMethod:获取类方法
  // IMP:方法实现
 
  // imageNamed
  // Class:获取哪个类方法
  // SEL:获取方法编号,根据SEL就能去对应的类找方法
 
  Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
 
  Method PH_imageNameMethod = class_getClassMethod([UIImage class], @selector(PH_imageNamed:));
 
  // 交换方法实现
  method_exchangeImplementations(imageNameMethod, PH_imageNameMethod);
}
 
  
 
}
 
// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
 
// 既能加载图片又能打印
+ (UIImage *)PH_imageNamed:(NSString *)imageName
{
  // 加载图片
  UIImage *image = [UIImage PH_imageNamed:imageName];
  // 2.判断功能
  if(image == nil) {
    NSLog(@"加载为空");
  }
 
  returnimage;
}
 
 
@end

3.动态添加方法

开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

@implementation ViewController
 
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
 
  Person *p = [[Person alloc] init];
 
  // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
  // 动态添加方法就不会报错
  [p performSelector:@selector(eat)];
 
}
 
 
@end
 
@implementation Person
// void(*)()

// 默认方法都有两个隐式参数,

默认一个方法都有两个参数,self,_cmd,隐式参数

 self:方法调用者
 _cmd:调用方法的编号
voideat(id self,SEL sel)
{
  NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
 
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.

// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
 
<!--动态添加方法,首先实现这个resolveInstanceMethod-->

<!-- resolveInstanceMethod调用:当调用了没有实现的方法没有实现就会调用resolveInstanceMethod-->

<!-- resolveInstanceMethod作用:就知道哪些方法没有实现,从而动态添加方法-->

<!-- sel:没有实现方法-->

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
 
  if(sel == @selector(eat)) {
    // 动态添加eat方法
 
    // 第一个参数:给哪个类添加方法
    // 第二个参数:添加方法的方法编号
    // 第三个参数:添加方法的函数实现(函数地址)
    // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
    class_addMethod(self, @selector(eat), eat, "v@:");
 
  }
 
  return[super resolveInstanceMethod:sel];
}
@end

4.给分类添加属性

原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

@implementation ViewController
 
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
 
  // 给系统NSObject类动态添加属性name
 
  NSObject *objc = [[NSObject alloc] init];
  objc.name = @"abc";
  NSLog(@"%@",objc.name);
}
@end
 
 
// 定义关联的key
staticconst char *key = "name";
 
- (void)setName:(NSString *)name
{
  // 添加属性,跟对象
  // 给某个对象产生关联,添加属性
  // object:给哪个对象添加属性
  // key:属性名,根据key去获取关联的对象 ,void * == id
  // value:关联的值
  // policy:策略
 
  objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{
 
  returnobjc_getAssociatedObject(self, @"name");
}

3> 相关函数

  • objc_msgSend : 给对象发送消息
  • class_copyMethodList : 遍历某个类所有的方法
  • class_copyIvarList : 遍历某个类所有的成员变量
  • 2.runtime有什么作用?

1.能动态产生一个类,一个成员变量,一个方法
2.能动态修改一个类,一个成员变量,一个方法
3.能动态删除一个类,一个成员变量,一个方法

3.常用的头文件

 #import <objc/runtime.h> 包含对类、成员变量、属性、方法的操作 #import <objc/message.h> 包含消息机制

4.常用方法

class_copyIvarList()返回一个指向类的成员变量数组的指针class_copyPropertyList()返回一个指向类的属性数组的指针

注意:根据Apple官方runtime.h文档所示,上面两个方法返回的指针,在使用完毕之后必须free()。

ivar_getName()获取成员变量名-->C类型的字符串property_getName()获取属性名-->C类型的字符串-------------------------------------typedef struct objc_method *Method;class_getInstanceMethod() class_getClassMethod()以上两个函数传入返回Method类型---------------------------------------------------method_exchangeImplementations()交换两个方法的实现

5.runtime在开发中的用途

1.动态的遍历一个类的所有成员变量,用于字典转模型,归档解档操作
代码如下:

- (void)viewDidLoad {      [super viewDidLoad];      /** 利用runtime遍历一个类的全部成员变量           1.导入头文件<objc/runtime.h>     */      unsigned int count = 0;    /** Ivar:表示成员变量类型 */      Ivar *ivars = class_copyIvarList([BDPerson class], &count);//获得一个指向该类成员变量的指针    for (int i =0; i < count; i ++) {        //获得Ivar        Ivar ivar = ivars[i];        //根据ivar获得其成员变量的名称--->C语言的字符串        const char *name = ivar_getName(ivar);          NSString *key = [NSString stringWithUTF8String:name];        NSLog(@"%d----%@",i,key);}}

运行结果如下:


成员变量遍历输出结果.png


获取一个类的全部属性:


获取类的属性的代码实现.png


结果如下:


输出结果.png


应用场景:

  • 可以利用遍历类的属性,来快速的进行归档操作。
  • 将从网络上下载的json数据进行字典转模型。

    注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法- (void)encodeWithCoder:(NSCoder *)encoder{        //归档存储自定义对象        unsigned int count = 0;      //获得指向该类所有属性的指针       objc_property_t *properties =     class_copyPropertyList([BDPerson class], &count);       for (int i =0; i < count; i ++) {            //获得            objc_property_t property = properties[i];        //根据objc_property_t获得其属性的名称--->C语言的字符串          const char *name = property_getName(property);      NSString *key = [NSString   stringWithUTF8String:name];          //      编码每个属性,利用kVC取出每个属性对应的数值               [encoder encodeObject:[self valueForKeyPath:key] forKey:key];  }}- (instancetype)initWithCoder:(NSCoder *)decoder{          //归档存储自定义对象            unsigned int count = 0;        //获得指向该类所有属性的指针          objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);          for (int i =0; i < count; i ++) {              objc_property_t property = properties[i];        //根据objc_property_t获得其属性的名称--->C语言的字符串              const char *name = property_getName(property);          NSString *key = [NSString stringWithUTF8String:name];        //解码每个属性,利用kVC取出每个属性对应的数值             [self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];  }    return self;}

    二、交换方法
    通过runtime的method_exchangeImplementations(Method m1, Method m2)方法,可以进行交换方法的实现;一般用自己写的方法(常用在自己写的框架中,添加某些防错措施)来替换系统的方法实现,常用的地方有:

  • 在数组中,越界访问程序会崩,可以用自己的方法添加判断防止程序出现崩溃数组或字典中不能添加nil,如果添加程序会崩,用自己的方法替换系统防止系统崩溃
  • ...
    代码实现如下:

    运行程序崩溃.png


    再次运行刚才的程序:
iOS Rumtime 之关联引用

节目预告 
1. 简单的关联引用
2. 为UIViewController 添加MBProgressHUD的HUB属性
3. 为UINavigationBar添加一个view属性 来完成动态改变UINavigationBar的外观

官方API是这样的, 下面这篇博客也是围绕这些来展开

// 关联策略枚举值typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {    OBJC_ASSOCIATION_ASSIGN = 0,               OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,     OBJC_ASSOCIATION_COPY_NONATOMIC = 3,      OBJC_ASSOCIATION_RETAIN = 01401,     OBJC_ASSOCIATION_COPY = 01403     };/**object               源对象key                关键字 唯一静态变量keyvalue               关联的对象 value(userAge)关键策略           OBJC_ASSOCIATION_COPY*/OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);// 通过 objc_getAssociatedObject获取关联对象OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);// 删除关联OBJC_EXPORT void objc_removeAssociatedObjects(id object)    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

情景1 :你要用分类为 User添加一个属性 叫做 userAge, User类在很多地方会用到, 而用户的年龄不常常被用到, 为了避免不必要的开销, 分类是个很好的选择.
首先我创建一个User类

@interface User : NSObject@property (nonatomic, copy) NSString *userName;@end
@implementation User@end

接下来我采用扩展的方式为User添加一个 userAge的属性.

@interface User (Extensions)@property (nonatomic, copy) NSString *userAge;@end
#import "User+Extensions.h"#import @implementation User (Extensions)static char userAgeKey;- (NSString *)userAge{    return objc_getAssociatedObject(self, &userAgeKey);}- (void)setUserAge:(NSString *)userAge{    objc_setAssociatedObject(self, &userAgeKey, userAge, OBJC_ASSOCIATION_COPY);}@end

如果单纯这样而使用关联引用, 我其实觉得很牵强, 表示恨不能理解…

接下来会给出在项目中”很好”的实践. 囧~.
情景2 : 为UIViewController 扩展一个 HUB属性, 接下来以 MBProgressHUD为例

#import @interface UIViewController (HUD)- (void)showHudInView:(UIView *)view hint:(NSString *)hint;@end
#import "UIViewController+HUD.h"#import "MBProgressHUD.h"#import static const void * httpReqHUDKey = &httpReqHUDKey;@implementation UIViewController (HUD)- (MBProgressHUD *)HUD{    return objc_getAssociatedObject(self, httpReqHUDKey);}- (void)setHUD:(MBProgressHUD *)HUD{    objc_setAssociatedObject(self, httpReqHUDKey, HUD, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (void)showHudInView:(UIView *)view hint:(NSString *)hint{    MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:view];    HUD.labelText = hint;    [view addSubview:HUD];    [HUD show:YES];    [self setHUD:HUD];}@end

可能看到这里有的同学已经明白了一点点, 说白了, 就是给原有的类扩展一个属性并且实现我们想要对属性进行的操作.

情景3 为系统UINavigationBar 扩展一个属性overlay(UIView) 来实现在很多App中流行的一个交互, 滑动界面的时候导航栏的显隐功能 — 类似于简书iOS端App那样的效果 . 代码来自一个很有名的三方库(LTNavigationBar). 反正3000+ 的Star. 没记错的话只有几十行代码, 想法非常的棒, 用到了关联属性, 在GitHub可以找到. 下面的效果图是我写的一个Demo, 你可以看图感受一下
这是我很久之前写的Demo


Objecive-C runtime实践-给Category添加属性


在开发中经常遇到需要添加hud的情形,每次添加的代码都在10行左右,遂新建一个ViewController的Category来添加hud。由于分类不能直接添加属性,就考虑到了runtime。
下面讲具体的实施步骤

在UIViewController+HUD.h中导入MBProgressHUD.h,

#import <UIKit/UIKit.h>#import "MBProgressHUD.h"@interface UIViewController (HUD)@end

在UIViewController+HUD.m中导入runtime.h,并添加hud属性:

#import "UIViewController+HUD.h"#import <objc/runtime.h>@interface UIViewController(Private)@property (nonatomic, strong) MBProgressHUD *progressHud;@end@implementation UIViewController (HUD)const char *kHudKey = "ProgressHUD_Key";@end

接下来是关键的一步-设置hud的setter与getter方法:

- (void)setProgressHud:(MBProgressHUD *)progressHud{    objc_setAssociatedObject(self, kHudKey, progressHud, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (MBProgressHUD *)progressHud{    MBProgressHUD *hud = objc_getAssociatedObject(self, kHudKey);    if (!hud) {        UIView *view = self.navigationController.view;        hud = [[MBProgressHUD alloc] initWithView:view];        hud.removeFromSuperViewOnHide = YES;        [view addSubview:hud];        self.progressHud = hud;    }    return hud;}

在setter中设置连接, 在getter中初始化。
好了,接下来就可以正常使用属性了,现在,我们对hud进行扩展。
写两个基本的show、hide方法,其余的实现在其基础上变化即可:

#pragma mark - Private- (void)showHudWithMessage:(NSString *)message model:(MBProgressHUDMode)mode{    if (self.progressHud.taskInProgress) {        return;    }    self.progressHud.taskInProgress = YES;    self.progressHud.mode = mode ?: MBProgressHUDModeIndeterminate;    self.progressHud.labelText = message;    self.progressHud.labelFont = [UIFont systemFontOfSize:15];    [self.progressHud show:YES];    [self.progressHud hide:YES afterDelay:15];}- (void)hideHudWithMessage:(NSString *)message image:(UIImage *)image{    self.progressHud.customView = [[UIImageView alloc] initWithImage:image];    self.progressHud.mode = MBProgressHUDModeCustomView;    self.progressHud.labelText = message;    self.progressHud.labelFont = [UIFont systemFontOfSize:15];    [self.progressHud show:YES];    [self performSelector:@selector(hideHud) withObject:nil afterDelay:0.7];}

再实现下面的方法大概就够用了:

- (void)showHud;- (void)showHudWithMessage:(NSString *)message;- (void)showDeterminateHudWithMessage:(NSString *)message;- (void)changeHudProgress:(double)precent;- (void)hideHud;- (void)hideHudWithSuccessMessage:(NSString *)message;- (void)hideHudWithErrorMessage:(NSString *)message;

具体的实现直接看代码吧!
https://github.com/Xigtun/RuntimeDemo



0 0
原创粉丝点击