利用runtime为系统类添加属性、成员变量.......-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

利用runtime为系统类添加属性、成员变量.......

简介: 1️⃣runtime介绍: runtime是一套比较底层的纯C语言API, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码.

1️⃣runtime介绍:

runtime是一套比较底层的纯C语言API, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码.

比如说,下面一个创建对象的方法 :

1.[[ZSPerson alloc] init]

2.runtime :objc_msgSend(objc_msgSend(“ZSPerson” , “alloc”), “init”)

2️⃣runtime 用来干什么呢??用在那些地方呢?怎么用呢?

runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)

在程序运行过程中, 动态创建一个类(比如KVO的底层实现)

在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法

遍历一个类的所有成员变量(属性)\所有方法

例如:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置!

例如,ZSPerson.h的文件如下所示

@interfaceZSPerson:NSObject

@property(nonatomic,assign)intage;

@property(nonatomic,assign)intheight;

@property(nonatomic,copy)NSString*name;

@property(nonatomic,assign)intage2;

@property(nonatomic,assign)intheight2;

@property(nonatomic,assign)intage3;

@property(nonatomic,assign)intheight3;

@property(nonatomic,assign)intage4;

@property(nonatomic,assign)intheight4;

@end

而ZSPerson.m实现文件的内容如下

#import"ZSPerson.h"

@implementationZSPerson

(void)encodeWithCoder:(NSCoder )encoder 

{

unsignedintcount =0;

Ivar ivars = class_copyIvarList([ZSPerson class], &count);

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

// 取出i位置对应的成员变量

Ivar ivar = ivars[i];

// 查看成员变量

constchar*name = ivar_getName(ivar);

// 归档

NSString*key = [NSStringstringWithUTF8String:name];

idvalue = [selfvalueForKey:key]; 

  [encoder encodeObject:value forKey:key]; 

  }

free(ivars);

    } 

  (id)initWithCoder:(NSCoder *)decoder 

  {

if(self= [super init]) {

unsignedintcount =0;

 Ivar *ivars = class_copyIvarList([ZSPerson class], &count);

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

// 取出i位置对应的成员变量

Ivar ivar = ivars[i];

// 查看成员变量

const char*name = ivar_getName(ivar);

// 归档

NSString*key = [NSStringstringWithUTF8String:name];

id value = [decoder decodeObjectForKey:key];

// 设置到成员变量身上

[selfsetValue:value forKey:key];

free(ivars);

  }

returnself; 

  }

@end

这样我们可以看到归档和解档的案例其实是runtime写下的

学习,runtime机制首先要了解下面几个问题

1.相关的头文件和函数

a> 头文件

利用头文件,我们可以查看到runtime中的各个方法!

b> 相关应用

NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)

字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)

KVO(利用runtime动态产生一个类)

用于封装框架(想怎么改就怎么改)

这就是我们runtime机制的只要运用方向

c> 相关函数

objc_msgSend : 给对象发送消息

class_copyMethodList : 遍历某个类所有的方法

class_copyIvarList : 遍历某个类所有的成员变量

class_…..

这是我们学习runtime必须知道的函数!

2.必备常识

a> Ivar : 成员变量

b> Method : 成员方法

从上面例子中我们看到我们定义的成员变量,如果要是动态创建方法,可以使用Method。

3️⃣接下来我们进行项目实战

首先给UITableViewCell创建一个分类RightDownPlugin

img_b467a02d1ddf748f3df66666c1b20a19.jpe

.h文件中

#import<UIKit,UIKit.h>

@interfaceUITableViewCell(RightDownPlugin)

@property(nonatomic,strong)UIImageView*statusImgV;//状态图@property(nonatomic,strong)UILabel*statusLab;//状态label

@end

.m文件

#import"UITableViewCell+RightDownPlugin.h"

#include <objc/runtime.h>

@implementationUITableViewCell(RightDownPlugin)

//定义常量 必须是C语言字符串 因为runtime是C语言API,

staticchar*statusImgKey ="statusImgKey";

staticchar*statusLabKey ="statusLabKey";

/* 

OBJC_ASSOCIATION_ASSIGN;            //assign策略

OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略

OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略

OBJC_ASSOCIATION_RETAIN;

OBJC_ASSOCIATION_COPY;

*//*

id object 给哪个对象的属性赋值

const void *key 属性对应的key

id value  设置属性值为value

objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择nonatomic

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

*/// 然后就需要自定义set和get方法了,因为category默认是不能添加属性的,

- (void)setStatusImgV:(UIImageView*)statusImgV{    objc_setAssociatedObject(self,statusImgKey,statusImgV,OBJC_ASSOCIATION_RETAIN);

}

- (UIImageView*)statusImgV{

return objc_getAssociatedObject(self, statusImgKey);

}

// Lab

- (void)setStatusLab:(UILabel*)statusLab{    objc_setAssociatedObject(self,statusLabKey,statusLab,OBJC_ASSOCIATION_RETAIN);

}

- (UILabel*)statusLab{

return objc_getAssociatedObject(self, statusLabKey);

}

@end

runtime常见的用法总结

1.runtime动态添加属性

需求:给NSString类添加两个属性:name和count

NSString+String.h中

#import<Foundation/Foundation.h>

/**

*  @property在分类里添加一个属性 不会有setter getter方法 只声明了一个变量 而且 这样声明 这个属性和这个类没有什么关系 */

@interface NSString (String)

@property (copy, nonatomic, nullable) NSString *name;

@property (copy, nonatomic, nullable) NSString *count;

@end

NSString+String.m中

#import "NSString+String.h"

#import<objc/message>

/**

*  动态添加属性的本质是 指向外部已经存在的一个属性 而不是去在对象中创建一个属性

*/

@implementation NSString (String)

static NSString *_name;

//在分类里声明属性 需要自己写set方法和get方法

- (void) setName:(NSString *)name

{

_name = name;

}

- (NSString *) name

{

return _name;

}

//添加属性 应该是与对象有关

- (void) setCount:(NSString *)count

{

//set方法里设置关联

//Associated 关联 联系

//跟某个对象产生关联,添加属性

/**

* id obj 给哪个对象添加属性(产生关联)

* const void *key 属性名 (根据key获取关联的对象) void * 相当于 id 万能指针 传c或者oc的都可以

* id value 要关联的值

* objc_AssociationPolicy policy 策略 宏对应assign retain copy (因为weak没有用 外面赋值完马上就会被销毁 所以没有weak)

*/

objc_setAssociatedObject(self, @"count", count, OBJC_ASSOCIATION_ASSIGN);

}

- (NSString *) count

{

//get方法里获取关联

return [NSString stringWithFormat:@"%ld",[objc_getAssociatedObject(self, @"count") length]];

}

@end

2.runtime动态加载方法

ViewController中

#import "ViewController.h"

#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{    [super viewDidLoad]; 

  //performSelector:动态添加方法   

Person *p0 = [[Person alloc] init];  

[p0 performSelector:@selector(eat)];

//动态添加方法 但是如果没有实现该方法 还是会崩溃 

[p0 performSelector:@selector(drink:) withObject:@"juice"];

//动态添加方法 但是如果没有实现该方法 还是会崩溃

}

Person.m中

#import "Person.h"

#import<objc/message.h>

//默认一个方法都有两个参数:self 和_cmd  self是方法的调用者 _cmd就是调用方法的编号(方法名) 这两个参数为隐式参数 但是如果调用的是c的函数 则需要写出来

@implementation Person

//定义函数 该函数名是啥都可以

void eat(id self,SEL _cmd)

{

//无返回值 两个参数 void(id,SEL) v@:

NSLog(@"%@调用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身没发打印 只能打印方法名

}

void drink(id self,SEL _cmd,id param1)

{

//v void    @ 对象    : 方法编号

NSLog(@"%@调用了%@方法 传递参数:%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身没发打印 只能打印方法名

}

//1.动态添加方法 首先要实现resolveInstanceMethod:方法或resolveClassMethod:方法

//前者对应实例方法 后者对应类方法

//这两个方法的作用是要知道哪个方法没有被实现

//这两个方法是在当该类的某个方法没有实现,但是又被外界调用了的时候调用 (及:外界试用performSelector:调用了该类中某个没有实现的方法)

//sel参数为没有被实现的这个方法

+ (BOOL) resolveInstanceMethod:(SEL)sel

{

//打印该方法名

//    NSLog(@"%@",NSStringFromSelector(sel));

//动态添加方法

if ([NSStringFromSelector(sel) isEqualToString:@"eat"])//sel == @selector(eat)也可以 但是会报警

{

/**

*  Class 给哪个类添加方法

*  sel 要添加的方法编号(方法名)

*  IMP 方法的实现 ———— 函数的入口(函数的指针 函数名 是啥都可以 不一定和sel相同)

*  types 方法的类型 编码格式 (类型c语言的字符串) (函数的类型:返回值类型 参数类型 直接查文档 文档有表格)

*/

class_addMethod([self class], sel, (IMP)eat, "v@:");

//处理完了要返回YES

//        return YES;

}

else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒号

{

class_addMethod([self class], sel, (IMP)drink, "v@:@");

//        return YES;

}

//由于不知道返回的YES还是NO 所以:

return [super resolveInstanceMethod:sel];

}

+ (BOOL) resolveClassMethod:(SEL)sel

{

return [super resolveClassMethod:sel];

}

@end

3.runtime交换方法(ios黑魔法)

ViewController.m中

#import "ViewController.h"

#import<objc/message.h>

#import "Person.h"

//#import "UIImage+image.h"//交换方法时候不用导入也可以 因为交换写在类+(void)load里

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{    [super viewDidLoad]; 

  /** 

  *  交换方法  

*///  

UIImage *image = [UIImage ov_imageNamed:@"123"];  

UIImage *image1 = [UIImage imageNamed:@"123"];  

UIImage *image2 = [UIImage imageNamed:@"123"];

  //用imageNamed加载图片,并不知道图片是否加载成功  

//需求:在以后调用imageNamed的时候,要知道图片是否加载成功  

//交换方法的实现 (把imageNamed:方法和ov_imageNamed:方法交换 及 调用imageNamed就是调用ov_imageNamed)

}

@end

UIImage+image.m中

#import"UIImage+image.h"

#import<objc/message.h>

@implementation UIImage (image)

//加载这个分类的时候调用

+ (void) load

{

NSLog(@"%s",__func__);

//方法都定义在类里面 所以 交换对象方法也用class_开头

/**

*  class_getMethodImplementation 获取类方法的实现

*

*  Class 获取哪个类的方法

*  SEL 获取哪个方法

*  class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  class_getInstanceMethod 获取对象方法

*

*  Class 获取哪个类的方法

*  SEL 获取哪个方法

*

*  class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  class_getClassMethod 获取类方法

*

*  class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  method_exchangeImplementations交换方法

*

*  Method m1  要被替换的方法

*  Method m2  要替换Method m1的方法

*  method_exchangeImplementations(<#Method m1#>, <#Method m2#>)

*/

//交换方法的实现

//1.拿到两个方法

Method imageNamedMethod = class_getClassMethod([self class], @selector(imageNamed:));

Method ov_imageNamedMethod = class_getClassMethod([self class], @selector(ov_imageNamed:));

//2.交换

method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);

}

/**

*  分类没有父类 没有super

*/

//+ (nullable __kindof UIImage *) imageNamed:(nonnull NSString *)imageName

//{

//    return nil;

//}

/**

*  用其他方法做 这个方法不好的原因是 1.导入头文件太蛋疼 2.团队其他人可能不知道

*/

+ (nullable __kindof UIImage *) ov_imageNamed:(nonnull NSString *)imageName

{

//1.加载图片功能

//    UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交换 所以这里再调用该方法就会造成死循环

UIImage *image = [UIImage ov_imageNamed:imageName];//此处直接调用方法本身即可

NSLog(@"%s %d",__func__,__LINE__);

//2.判断返回是否为空功能

if (!image)

{

//NSException 为抛异常(强制崩溃)

//        NSException *e = [NSException

//                          exceptionWithName: @"异常情况"

//                          reason: @"图片为空"

//                          userInfo: nil];

//        @throw e;

}

else

{

}

return image;

}

@end

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: