您的位置:首页 > 其它

Runtime 的一些用法

2016-10-14 18:12 162 查看
runtime 就是OC 中经常说的 运行时

这里 简单介绍一下 OC 中用到一些场景

1、字典转模型  

2、给分类添加关联对象

3、交换方法

runtime  使用的时候一般建立一个 NSObject 的分类Cotegory 。 当然也可以根据 实际情况创建其他类的Cotegory。在创建的文件里面需要导入

#import <objc/runtime.h>


一、字典转模型。

首先 动态的获取 类 的属性名称 -> 然后使用 KVC 进行赋值.

1、首先获取类的属性名称

.h 

/**
获取 属性的名称数组
@return  返回对象的属性名称数组
*/
+ (NSArray *)cz_objPropertiesAry;
.m

const void *kPropertiesKey = "kPropertiesKey";

+ (NSArray *)cz_objPropertiesAry
{
//获取属性数组的指针数组
unsigned int count = 0;
objc_property_t *property = class_copyPropertyList([self class], &count);

//创建存放数据的数组
NSMutableArray *array = [NSMutableArray array];

//遍历属性数组
for (unsigned int i = 0; i < count; i++) {

// 指针   C语言中数组的名字 为第一个元素的指针
objc_property_t pty = property[i];

//获取属性的名字  类型为C的字符串
const char *cName  =  property_getName(pty);

//把C字符串转化为OC字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];

//[NSString stringWithUTF8String:cName]; 也可以

//把信息添加到数组里面
[array addObject:name];

}

//释放属性数组
free(property);

return array.copy;
}


unsigned int count =0; 
//下面为数组
 objc_property_t *proList = class_copyPropertyList([selfclass],
&count);
注意,这个方法获取的数组最后要用  free(proList);  释放掉

class_copyPropertyList
 获取 类的属性的列表
class_copyIvarList
  获取类的 成员变量的列表

class_copyMethodList 
获取类 的方法的列表

class_copyProtocolList
 获取类的代理的列表
参数 
1、 要获取的这个类 因为是Cotegory 分类 ,使用 self
2、列表里面元素的个数      unsigned int  无符号的整形
返回值:
所有属性的数组      类型为 objc_property_t 的数组

再遍历类的属性数组 ,因为类型为   objc_property_t  是一个指针,使用property_getName获取这个指针对应属性的名字。
使用property_getName 获取的是一个const char  类型C 语言的字符串  再转化为OC的字符串 。最后添加到存储数据的数组里面。

获取到类的属性名称后,就可以使用KVC 就行赋值了。

//所有字典转模型框架 核心算法
+ (instancetype)cz_objcWithDictionary:(NSDictionary *)dict
{
//实例化对象
id object = [[self alloc]init];

//获取对象的属性名称数组
//1> 获得 self 的属性列表
NSArray *array = [self cz_objPropertiesAry];

//遍历字典  使用KVC 为数组中的属性赋值
[dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

//需要先判断数组中是否包含 字典的Key
if ([array containsObject:key]) {

//赋值
[object setValue:obj forKey:key];
}
}];

return object;
}


使用:
创建一个Person 类  不实现.m

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double height;
@property (nonatomic, copy) NSString *title;

@end


在ViewController 里面
导入  
 #import "Person.h"     
#import "NSObject+Runtime.h"

//获取Persion的属性列表数组
NSArray *properties =   [Person cz_objPropertiesAry];
NSLog(@"%@",properties);

NSDictionary *dic = @{@"name":@"张三",@"age":@(22),@"height":@(168),@"title":@"运行时",@"place":@"boss"};
Person *persion = [Person cz_objcWithDictionary:dic];
NSLog(@"%@",persion);


如果因为iOS 在运行的时候 里面的类的对象不会改变,重复的获取 类 的属性名称 会造成 影响APP性能,这个时候可以使用关联对象的方法。

二、添加关联 动态的添加属性值

修改获取类属性的方法

const void *kPropertiesKey = "kPropertiesKey";

+ (NSArray *)cz_objPropertiesAry
{

#pragma mark 关联对象 获取 属性值 没有 则添加属性值  (动态的添加属性值)
/*
此方法虽然能够获取类的属性数组  但是如果每次调用都要执行一次的话 耗费的时间长,
使用 关联对象  动态的添加属性值  分别在方法的开头和结尾

*/
/*
参数:
1、对象  self
2、const void 的key

返回值  : Id类型
添加的属性值
*/

NSArray *proList =  objc_getAssociatedObject(self, kPropertiesKey);
if (proList) { //如果获取的关联对象里面有元素 就直接返回
return proList;
}

//获取属性数组的指针数组
unsigned int count = 0;
objc_property_t *property = class_copyPropertyList([self class], &count);

//创建存放数据的数组
NSMutableArray *array = [NSMutableArray array];

//遍历属性数组
for (unsigned int i = 0; i < count; i++) {

// 指针   C语言中数组的名字 为第一个元素的指针
objc_property_t pty = property[i];

//获取属性的名字  类型为C的字符串
const char *cName  =  property_getName(pty);

//把C字符串转化为OC字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];

//[NSString stringWithUTF8String:cName]; 也可以

//把信息添加到数组里面
[array addObject:name];

}

//释放属性数组
free(property);

#pragma mark 关联对象 2 添加属性值 动态的添加属性值

/*
参数:
1、对象 self
2、const void 的key  同第一步
3、添加的属性值
4、关联的协议
*/
objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

return array.copy;
}


修改的方法 里面 多了几行代码

NSArray *proList =  objc_getAssociatedObject(self, kPropertiesKey);
if (proList) {
return proList;
}


objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);


分别在方法的开始和结尾

objc_getAssociatedObject    获取关联对象动态添加的属性值
参数:
1、对象 self
2、const void 的key 
返回值:
Id类型, 添加的属性值

objc_setAssociatedObject 设置动态添加关联对象的属性值
参数:
1、对象 self 
2、const void 的key 跟上面获取的key一致
3、要添加的属性值,(如果设置了,上面的objc_getAssociatedObject 就能直接获取到,下次调用就能直接获取属性值)
4、关联的协议   OBJC_ASSOCIATION_RETAIN_NONATOMIC

添加了 了关联对象 的方法,下次调用该方法的时候,运行到objc_getAssociatedObject 就能直接获取到 数据了,不用再往下执行了。

三、交换方法

特点: 
在无法修改系统方法 ,和第三方框架的时候,
1、利用交换方法,先执行自定义的方法
2、再执行系统方法或第三方框架方法。
被称之为 黑魔法 ,对系统和框架有很强的依赖性。

举个例子:

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
imageView.center = self.view.center;
[self.view addSubview:imageView];

imageView.image = [UIImage imageNamed:@"image5.png"];


在UIImageView 的时候,系统的设置图片的方法, imageView.image = [UIImage imageNamed:@""]; 

imageView.image  相当于 imageView setImage: UIImage
我们可以自定义一个方法,替换掉系统的setImage: 方法
创建一个UIImageView 的Cotegory

//在类 被加载到运行时的时候,就会执行
+ (void)load{

//交叉方法  就下面的3句话

//获取 类 实例化的原始方法   setImage:
Method originalMethod = class_getInstanceMethod([self class], @selector(setImage:));

//获取 自定义的类的实例化的方法  cz_setImage:
Method swizzleMethod = class_getInstanceMethod([self class], @selector(cz_setImage:));

//交换两个方法 setImage: 和 cz_setImage:  完成之后
//1> 调用setImage: 相当于调用 cz_setImage:
//2> 调用 cz_setImage: 相当于调用 setImage:
method_exchangeImplementations(originalMethod, swizzleMethod);
}

//自定义 的 类 的实例化方法  Cotegory
- (void)cz_setImage:(UIImage *)image
{
//这里是我们想要做的事情
NSLog(@"调用的自定义的方法: %s",__FUNCTION__);

<span style="white-space:pre">	</span>
//再调用系统 的默认方法
/*重点 :
为什么 方法名是自定义方法 而不是系统默认的方法名字
是因为load 里面 系统默认方法和自定义的方法进行了交换
系统默认的方法 setImage: 变成了 cz_setImage:
所以 在交换方法完成之后 再次调用系统默认的方法就变成了 我们自定义的方法

*/
[self cz_setImage:result];
}


首先先自定义一个设置图片的方法- (void) cz_setImage:(UIImage *)image 

里面 输出 当前的方法。

在方法 + (void) load{} 里面添加 我们的交换方法
就3句话,
1、获取系统默认的 设置图片的方法
2、获取自定义的设置图片的方法
3、交换这两个方法

class_getInstanceMethod  获取类的实例的方法
参数:
1、类的实例  [self class]   (这是一种特殊的实例化对象)
2、获取的实例方法  SEL 
返回值:
Method  方法 类型

method_exchangeImplementations
参数:
1、要交换的第一个方法
2、要交换的第二个方法

重点注意:
在方法交换之后,两个方法已经被交换了,调用系统方法 变成调用自定义的方法名
调用自定义的方法 变成调用系统的方法名

所以下面:

//自定义 的 类 的实例化方法  Cotegory
- (void)cz_setImage:(UIImage *)image
{
//这里是我们想要做的事情  输出当前调用的方法
NSLog(@"调用的自定义的方法: %s",__FUNCTION__);

<span>	</span>
//再调用系统 的默认方法
/*重点 :
为什么 方法名是自定义方法 而不是系统默认的方法名字
是因为load 里面 系统默认方法和自定义的方法进行了交换
系统默认的方法 setImage: 变成了 cz_setImage:
所以 在交换方法完成之后 再次调用系统默认的方法就变成了 我们自定义的方法

*/  这个时候系统默认的方法名 在这里变成了   <span style="font-family: Arial, Helvetica, sans-serif;">cz_setImage:</span>

[self cz_setImage:result];
}


ViewController 的运行结果为

#import "UIImageView+RuntimeCrossoverMethod.h"

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
imageView.center = self.view.center;
[self.view addSubview:imageView];

imageView.image = [UIImage imageNamed:@"image5.png"];


2016-10-14 16:38:01.248 Runtime[4947:837294]调用的自定义的方法: -[UIImageView(RuntimeCrossoverMethod) cz_setImage:]

我们调用系统默认的方法 变成调用我们自定义的方法,当然我们在自定义方法里面又掉用了系统的方法(这个时候系统的方法名变成了我们自定义的方法名)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  runtime