RunTime实战小结
- runtime 简称运行时,OC就是运行时机制,也就是在运行时的一些机制,其中最主要的是消息机制
- 对于C语言,函数的调用在编译的时候会决定调用哪个函数。
- 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正的调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
- 事实证明
- 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
- 在编译阶段,C语言调用未实现的函数就会报错。
Runtime作用
0.使用前准备
- 使用runtime的消息发送机制首先要引入#import <objc/message.h>头文件,此外需要将Build Setting--> Apple LLVM 9.0 - Preprocessing--> Enable Strict Checking of objc_msgSend Calls改为NO
发送消息(消息机制)
- 对象模型调用私有方法
创建Person对象, 其.h .m代码分别为
.h
- (void)eat;
- (void)run:(NSInteger)meter;
.m
- (void)eat
{
NSLog(@"开吃");
}
- (void)run:(NSInteger)meter
{
NSLog(@"跑了%ld米", meter);
}
在外部通过runtime调用方法为
Person * per = [[Person alloc] init];
objc_msgSend(per, @selector(eat)); //调用无参数的方法
objc_msgSend(per, @selector(run:), 20); //调用有参数的方法
- 方法调用流程
- 通过isa去对应的类中查找方法
- 注册方法编号
- 根据方法编号去查找对应的方法
- 找到只是最终函数实现的地址,根据地址去方法区调用函数
交换方法
当我们想要更换掉系统的方法来时我们可以通过runtime来实现方法的交换,代码如下
Method eatMethod = class_getInstanceMethod([self class], @selector(eat));
Method sayMethod = class_getInstanceMethod([self class], @selector(say));
method_exchangeImplementations(eatMethod, sayMethod);
交换的方法
- (void)eat
{
NSLog(@"开吃");
}
- (void)say
{
NSLog(@"hai");
[self say];
}
动态添加属性
当我们给系统对象添加属性时可通过创建Category类并在.h中声明希望添加的分类名称,但是其只是声明了get和set方法,必不会对其进行实现,也不会生成下滑线的成员属性,由于其不会生成属性,所以也没必要加一些策略性描述。代码如下:
.h
@property NSString * name;
由于.h中仅对分类添加名称和声明,并没有进行实现,所以在.m中需要我们自己实现其set、get方法
#import <objc/message.h>
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
在需要使用的地方导入声明的头文件就能正常的进行动态添加的属性存取了
runtime调用对象的方法
- runtime可以实现通过字符串进行私有或者公共方法的调用
Person * per = [[Person alloc] init];
SEL eatSelector = NSSelectorFromString(@"eat");
BOOL eatSelectorResult = [per respondsToSelector:eatSelector]; //检测Person类是否实现了该方法,否则调用了未实现的方法会出现崩溃的情况
NSLog(@"方法检测结果 %d", eatSelectorResult);
if (eatSelectorResult) {
objc_msgSend(per, eatSelector);
}
- 虽然respondsToSelector在api中为对象方法,但是我们也可以通过类来调用该方法来判断某个类方法是否实现
//类方法的调用方式
SEL sayHaiSelector = NSSelectorFromString(@"sayHai");
BOOL sayHaiClassSelectorResult = [Person respondsToSelector:sayHaiSelector];
NSLog(@"类方法检测结果 %d", sayHaiClassSelectorResult);
if (sayHaiClassSelectorResult) {
objc_msgSend([Person class], sayHaiSelector);
}
- 当在方法中包含有对象时我们改怎么传递呢,代码如下:
//带有参数化的方法调用方法
SEL selectDrink = NSSelectorFromString(@"drink:");
BOOL drinkSelector = [per respondsToSelector:selectDrink];
if (drinkSelector) {
objc_msgSend(per, selectDrink, @"wine");
}
- 如果代码中有多个参数该怎么调用呢?对象方法类似,代码如下:
//带有多参数的方法调用方法
SEL drinkAndEatSelector = NSSelectorFromString(@"drink:food:");
BOOL drinkAndEatSelectorResult = [per respondsToSelector:drinkSelector];
if (drinkAndEatSelectorResult) {
objc_msgSend(per, drinkAndEatSelector, @"wine", @"bread");
}
在以上方法调用之前切记要进行实现判断,防止出现因为未实现方法而出现崩溃的情况
此外,想获取对象当前方法列表可通过如下方法实现:
Person * per = [[Person alloc] init];
unsigned int outCountMethod = 0;
Method * methods = class_copyMethodList([per class], &outCountMethod);
for (int j = 0; j < outCountMethod; j++) {
Method method = methods[j];
SEL methodSEL = method_getName(method);
const char * selName = sel_getName(methodSEL);
if (methodSEL) {
NSLog(@"sel------%s", selName);
}
}
在上面获取的方法列表中,仔细观察的话会发现除正常方法和set方法外还存在一个名称为.cxx_destruct的方法,这个方法与ARC机制的自动管理内存有关,有兴趣的同学可以查下看看,暂时不做研究。
runtime实现model转字典
- 遇到简单的model我们可以很方便的实现model转字典,代码如下:
Person * per = [[Person alloc] init];
per.name = @"gpf";
per.age = 13;
per.height = 180.4;
NSMutableDictionary * perDic = @{}.mutableCopy;
unsigned int outCount = 0;
objc_property_t * properties = class_copyPropertyList([per class], &outCount);
for (unsigned int index = 0; index < outCount; index++)
{
objc_property_t property = properties[index];
//属性名
const char * name = property_getName(property);
//属性描述
const char * propertyAttr = property_getAttributes(property); //属性的类型
NSLog(@"属性描述为 %s 的 %s ", propertyAttr, name);
NSString * nameStr = [NSString stringWithUTF8String:name];
id propertyValue = [per valueForKey:nameStr];
NSLog(@"属性的值为 %@", propertyValue);
[perDic setObject:propertyValue forKey:nameStr];
}
free(properties);
NSLog(@"生成的字典为 %@", perDic);
- 上面的代码可以实现简单的model转化为字典,但当我们碰到model下存在另外一个model时就无法实现对二级model进行转化,这时我们可以通过改变上面的方法实现对多级model的转换:
- (NSDictionary*)dicFromObject:(NSObject*)object {
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([object class], &count);
if(count == 0){
free(propertyList);
return nil;
}
for(int i =0; i < count; i++) {
objc_property_t property = propertyList[i];
const char * cName =property_getName(property);
NSString *name = [NSString stringWithUTF8String:cName];
NSObject*value = [object valueForKey:name];
if(value ==nil) {
[dic setObject:[NSNull null] forKey:name];
}else{
if([self dicFromObject:value]){
//model
[dic setObject:[self dicFromObject:value] forKey:name];
}else{
[dic setObject:value forKey:name];
}
}
}
free(propertyList);
NSLog(@"生成的字典为 %@", dic);
return[dic copy];
}
上面是简单的通过runtime实现model对象转字典,下面实现一下简单的一层字典转model的方法
//下面是在NSObject的category中添加的代码
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
// 创建对应模型对象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
for (int i = 0; i < count; i++) {
// 2.1 获取成员属性
Ivar ivar = ivarList[i];
// 2.2 获取成员属性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成员属性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出对应value给模型属性赋值
id value = dict[key];
// 2.5 KVC字典转模型
if (value) {
[objc setValue:value forKey:key];
}
}
// 返回对象
return objc;
}
以上代码若有错误烦请拍砖