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
.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