OC语法Objective-CiOS之runtime

runtime的作用及实例

2015-09-29  本文已影响1148人  嗯o哼

什么是runtime

runtime是底层的纯C语言的API,它包含的很多底层的语法。
我们平时编写的OC代码,在程序运行过程中,最终都是转化成了runtime的C语言代码,
runtime也称为运行时,它是OC的幕后工作者。

runtime的作用

runtime主要就是做一些底层的操作,如:
   1. 动态的添加对象的成员变量和方法
   2.动态交换两个方法的实现(可以替换系统的方法)
   3.获得某个类的所有成员方法、所有成员变量
   4. 实现分类也可以添加属性
   5.实现NSCoding的自动归档和解档
   6.实现字典转模型的自动转换

替换系统方法,可以通过拦截系统的方法探究底层,比如block 的实现原理

常用方法

1.获取类中的方法

Method class_getClassMethod(Method cls , SEL name)

如:

Method m = class_getClassMethod([Person class],@selector(setName:));

2.获取对象中的方法

Method class_getInstanceMethod(Method cls, SEL name)

如:

Person *person = [[Person alloc] init];
Method m = get_InstanceMethod([person class],@selector(setName:));

3.交换两个方法的实现

 void method_exchangeImplementations(Method m1,Method m2)

     Person *p =[[Person alloc] init];
    [p study];
    [p run];
    //交换实现
    //instance method :实例方法,
    //class_getInstanceMethod得到实例的方法(即对象方法)
    //两个参数 1:类名 2.方法名
    //class_getClassMethod :得到实例化的方法
    Method m1 = class_getInstanceMethod([Person class], @selector(study));
    Method m2 = class_getInstanceMethod([Person class], @selector(run));
    method_exchangeImplementations(m2, m1);
    [p study];
    [p run];

具体操作


051B23EE-AFC6-4C95-9297-1E58708D5B96.png

4.获取成员变量

Ivar  *ivars = class_getCopyIvarList(Ivar ivar);

实现分类中添加属性

为所有的NSObject对象添加属性
1.首先创建一个NSObject分类NSObject+Extension
2.在.h中使用@property添加属性

此时使用@property添加数属性,并非真正的属性,如果此时调用查看属性,将会崩溃,
因为分类并未实现添加添加属性的功能,想要添加属性,需要使用runtime,动态的添加

3.在.m文件中实现getter和setter方法

如果想要添加多个属性,就需要在每个对象中抽出一块空间用于存放属性,
使用objc_setAssociatedObject方法进行关联

#import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
//用于存放属性的变量,多个属性,需要创建不同的变量
char BookKey;
-(void)setBooks:(NSArray *)books{
objc_setAssociatedObject(self, &BookKey, books, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSArray *)books{
return objc_getAssociatedObject(self, &BookKey);
}
@end

遵守协议NSCoding,实现属性的自动归档与解档

需求分析:
当想要对象自动进行归档解档的时候,如果属性非常的多,一个一个天添加[encoder encodeObject:@(xxx) forKey:@"_xxx"];将会非常的繁琐。
既然能够获取所有的属性,我们就可以通过循环遍历属性的方式进行统一的归档和解档

- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
    // 用来存储成员变量的数量
    unsigned int outCount = 0;
    
    // 获得Dog类的所有成员变量
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    
    // 遍历所有的成员变量
    for (int i = 0; i<outCount; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获得key对应的值
        id value = [decoder decodeObjectForKey:key];
        
        // 设置到成员变量上
        [self setValue:value forKeyPath:key];
    }
    
    free(ivars);
}
return self;
}

/**
 * 将对象写入文件时会调用这个方法(开发者需要在这个方法中说明需要存储哪些属性)
 */
- (void)encodeWithCoder:(NSCoder *)encoder
{
// 用来存储成员变量的数量
unsigned int outCount = 0;

// 获得Dog类的所有成员变量
Ivar *ivars = class_copyIvarList([self class], &outCount);

// 遍历所有的成员变量
for (int i = 0; i<outCount; i++) {
    // 取出i位置对应的成员变量
    Ivar ivar = ivars[i];
    
    NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
    // 通过key获得对应成员变量的值
    id value = [self valueForKeyPath:key];
    
    [encoder encodeObject:value forKey:key];
}

free(ivars);
}

注意:

ARC的内存管理机制 只适合OC语法,对于C语言的内存还是需要手动的释放,当使用runtime的时候,
如果包含了copy、create、retain、new等词语,那么在最后就需要释放内存

使用free(对象)进行释放如:free(ivars);

利用runtime实现字典转模型

描述:

KVC的字典转模型具有一个缺陷,就是属性的数量与名称都必须保持一致,如果字典中的属性多,
而模型中没有使用KVC赋值的时候就会崩溃,需要实现另一个方法 
setValue:forUndefinedKey:方法,并如果对象中包含了另一个对象作为属性,
也将不能自动将其转化为模型

而使用runtime实现的字典转模型,可以实现将所有的对象都转化为对应的模型,
并且不会出现属性找不到,而奔溃的现象

NSObject+Extension.h
#import <Foundation/Foundation.h>
@interface NSObject (Extension)
-(void)setDiction:(NSDictionary *)dict;
+(instancetype)objectWithDiction:(NSDictionary *)dict;
@end

NSObject+Extension.m

#import "NSObject+Extension.h"
#import <objc/runtime.h>

@implementation NSObject (Extension)

-(void)setDiction:(NSDictionary *)dict{
//获取类
Class c = self.class;
//循环遍历 类 (本类 和所有的父类)
while (c && c != [NSObject class]) {
    //获取所有的属性
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList(c, &outCount);
    //遍历类中的属性
    for (int i = 0; i < outCount; i++) {
        //获取属性名
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //去掉key中的 _
        key = [key substringFromIndex:1];
        //通过key 获取属性的值
        id value = dict[key];
        //如果key是一个空值 退出本轮的循环
        //原因:如果字典中没有这个key,那么value将会是一个空值,kvc 不能赋值空值
        if (value == nil) {
            continue;
        }
        //如果类中包含另一个类为对象,也要将该对象进行字典转模型
        //获取对象的属性的类名
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        // 对象名会以@“名字”的形式 出现,但是同时字符串也是以这种形式表示,因此可以先判断type中是否包含 @ 符号
        NSRange range = [type rangeOfString:@"@"];
        //如果range.location 不等于NSNotFound说明 找到了@
        if (range.location != NSNotFound) {
            //截取type中的名字 去除@“ ”
            type = [type substringWithRange:NSMakeRange(2, type.length -3)];
            if (![type hasPrefix:@"NS"]) {
                //将type转化为类名
                Class class = NSClassFromString(type);
                value = [class objectWithDiction:value];
            }
            
        }
        
        //赋值
        [self setValue:value forKey:key];
        
    }
    //ARC 只适用于OC语法,C语言中的内存 需要手动释放
    free(ivars);
    c = [c superclass];
    NSLog(@"1");
  }
}
+(instancetype)objectWithDiction:(NSDictionary *)dict{
NSObject *obj = [[self alloc] init];
[obj setDiction:dict];
return  obj;
}  
@end
上一篇下一篇

猜你喜欢

热点阅读