ios学习--Objective-C runtime的使用

用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block)。开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做"消息传递"(Messaging)。当程序运行起来以后,为其提供相关支持的代码叫做"Objective-C运行期环境"(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。即我们写的oc代码,它在运行的时候是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。
  那什么是Objective-C runtime?
  简单来说,Objective-C runtime是一个实现Objective-C语言的C库。对象可以用C语言中的结构体表示,而方法(methods)可以用C函数实现。事实上,他们差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类,对象和它们的方法。
  runtime的常见方法
复制代码
   //返回一个指向类的成员变量数组的指针
    class_copyIvarList()
    //返回一个指向类的属性数组的指针
    class_copyPropertyList()
    注意:根据Apple官方runtime.h文档所示,上面两个方法返回的指针,在使用完毕之后必须free()。
    ---------------------------------------------------
    //获取成员变量名-->C类型的字符串
    ivar_getName()
    //获取属性名-->C类型的字符串
    property_getName()
    ---------------------------------------------------
    typedef struct objc_method *Method;
    class_getInstanceMethod()
    //以上两个函数传入返回Method类型
    class_getClassMethod()
    ---------------------------------------------------
    //交换两个方法的实现
    method_exchangeImplementations()
复制代码

  

  runtime在开发中的用途

1.动态的遍历一个类的所有成员变量,属性,方法,协议等,可用于字典转模型,归档解档操作

复制代码
    unsigned int count;

    //获取成员变量的结构体
    Ivar *ivars = class_copyIvarList([Person class], &count);

    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        //根据ivar获得其成员变量的名称
        const char *name = ivar_getName(ivar);
        //C的字符串转OC的字符串
        NSString *key = [NSString stringWithUTF8String:name];
        NSLog(@"%d == %@",i,key);
    }
    //记得释放
    free(ivars);

  //获得指向该类所有属性的指针
    objc_property_t *properties = class_copyPropertyList([Person class], &count);
    
    for (int i = 0; i < count; i++) {
        //获得该类的一个属性的指针
        objc_property_t property = properties[i];
        //获取属性的名称
        const char *name = property_getName(property);
        //将C的字符串转为OC的
        NSString *key = [NSString stringWithUTF8String:name];

        NSLog(@"%d == %@",i,key);
    }
    //记得释放
    free(properties);

  //获取指向该类所有方法的指针
    Method *methods = class_copyMethodList([Person class], &count);
    
    for (int i = 0; i < count; i++) {
        //获取该类的一个方法的指针
        Method method = methods[i];
        //获取方法
        SEL methodSEL = method_getName(method);
        //将方法转换为C字符串
        const char *name = sel_getName(methodSEL);
        //将C字符串转为OC字符串
        NSString *methodName = [NSString stringWithUTF8String:name];
        
        //获取方法参数个数
        int arguments = method_getNumberOfArguments(method);
        
        NSLog(@"%d == %@ %d",i,methodName,arguments);
    }
    //记得释放
    free(methods);

  //获取指向该类遵循的所有协议的指针
    __unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        //获取该类遵循的一个协议指针
        Protocol *protocol = protocols[i];
        //获取C字符串协议名
        const char *name = protocol_getName(protocol);
        //C字符串转OC字符串
        NSString *protocolName = [NSString stringWithUTF8String:name];
        NSLog(@"%d == %@",i,protocolName);
    }
    //记得释放
    free(protocols);
复制代码

应用场景:

  • 可以利用遍历类的属性,来快速的进行归档操作。
  • 将从网络上下载的json数据进行字典转模型。

复制代码
//注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count;
    //获得指向当前类的所有属性的指针
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        //获取指向当前类的一个属性的指针
        objc_property_t property = properties[i];
        //获取C字符串属性名
        const char *name = property_getName(property);
        //C字符串转OC字符串
        NSString *propertyName = [NSString stringWithUTF8String:name];
        //通过关键词取值
        NSString *propertyValue = [self valueForKey:propertyName];
        //编码属性
        [aCoder encodeObject:propertyValue forKey:propertyName];
    }
    //记得释放
    free(properties);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    unsigned int count;
    //获得指向当前类的所有属性的指针
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        //获取指向当前类的一个属性的指针
        objc_property_t property = properties[i];
        //获取C字符串属性名
        const char *name = property_getName(property);
        //C字符串转OC字符串
        NSString *propertyName = [NSString stringWithUTF8String:name];
        //解码属性值
        NSString *propertyValue = [aDecoder decodeObjectForKey:propertyName];
        [self setValue:propertyValue forKey:propertyName];
    }
    //记得释放
    free(properties);
    return self;
}
//重写description方法,打印出自定义的属性
- (NSString *)description{
    NSString *string = [NSString stringWithFormat:@"name=%@ \n age=%d \n apples=%@",_name,_age,_apples];
    return string;
}
复制代码

   归档解档方法

复制代码
        //自定义类
     Person *p = [[Person alloc] init];
        p.name = @"张三";
        p.age = 18;
        p.apples = @[@"iphone",@"ipad"];
        
        //归档
        NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"person.archiver"];
        BOOL success = [NSKeyedArchiver archiveRootObject:p toFile:filePath];
        if(success){
            NSLog(@"归档成功");
        }

        //解归档
        Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
        NSLog(@"%@",person);    
复制代码

 

字典转模型

复制代码
#import "NSObject+Item.h"
#import <objc/message.h>

@implementation NSObject (Item)

// 字典转模型
+ (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];
        
        // 获取成员属性类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        // 二级转换,字典中还有字典,也需要把对应字典转换成模型
        //
        // 判断下value,是不是字典
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { 
       //  是字典对象,并且属性名对应类型是自定义类型
            // user User
            
            // 处理类型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
     //自定义对象,并且值是字典
       // value :user 字典 -> User模型
    // 获取模型(user)类对象
    Class modalClass = NSClassFromString(ivarType);

    //字典转模型
    if(modalClass){

        value = [modalClass objectWithDict:value];

    }

        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
        // 判断值是否是数组

        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                
                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;
                
                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];
                
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel objectWithDict:dict];
                    [arrM addObject:model];
                }
                
                // 把模型数组赋值给value
                value = arrM;
                
            }
        }
        
        // 2.5 KVC字典转模型
        if (value) {
            
            [objc setValue:value forKey:key];
        }
    }
    
    // 返回对象
    return objc;
    
}

@end
复制代码

 

 2.交换方法,可以是不同类的方法,也可以是同类的

   Method one =  class_getInstanceMethod([Person0 class], @selector(oneMethod));

    Method two =  class_getInstanceMethod([Person1 class], @selector(twoMethod));

    method_exchangeImplementations(one, two);

3.添加方法

复制代码
- (void)sayFrom
{

    class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
    if ([self.person respondsToSelector:@selector(guess)]) {
        //Method method = class_getInstanceMethod([self.xiaoMing class], @selector(guess));
        [self.person performSelector:@selector(guess)];
        
    } else{
        NSLog(@"Sorry,I don't know");
    }
    self.textview.text = @"beijing";
}

void guessAnswer(id self,SEL _cmd){
    
    NSLog(@"i am from beijing");
    
}
复制代码

4.拦截调用动态添加

复制代码
/*
*  + (BOOL)resolveClassMethod:(SEL)sel;//调用不存在的类方法时返回No,可以加上自己的处理,返回Yes
*  + (BOOL)resolveInstanceMethod:(SEL)sel;//跟上面的类似,不过处理的是实例方法
*/

//首先从外部隐式调用一个不存在的方法:
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

//然后,在target对象内部重写拦截调用的方法,动态添加方法。
void runAddMethod(id self, SEL _cmd, NSString *string){
    NSLog(@"add C IMP ", string);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    //给本类动态添加一个方法
    if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
        class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
    }
    return YES;
}
复制代码

 

5.方法上加功能

复制代码
@implementation UIButton(count)
//load方法会在类第一次被加载的时候调用
+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class selfClass = [self class];
        
        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
        
        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
        
        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }
        
    });
}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [[Tool sharedManager] addCount];
    [self mySendAction:action to:target forEvent:event];
}
复制代码

6.动态变量控制

复制代码
  unsigned int count = 0;
    Ivar *ivar = class_copyIvarList([self.person class], &count);
    for (int i = 0; i<count; i++) {
        Ivar var = ivar[i];
        const char *varName = ivar_getName(var);
        NSString *proname = [NSString stringWithUTF8String:varName];
        
        if ([proname isEqualToString:@"_name"]) {   //这里别忘了给属性加下划线
            object_setIvar(self.person, var, @"daming");//使用runtime将.name属性的值修改
            break;
        }
    }
    NSLog(@"XiaoMing change name  is %@",self.person.name);
复制代码

 

7.拓展属性

复制代码
/*
*在开发中经常需要给已有的类添加方法和属性,但是Objective-C是不允许给已有类通过分类添加属性的,因为类分类是不会自动生成成员变量的。但是,我们可以通过运行时机制就可以做到了。
*/
@interface NSObject(height)

//头文件中声明一个属性
@property (nonatomic, assign) double height;

@end

@implementation NSObject(height)

static double heightKey;//用来参考

-(void)setHeight:(double)height
{

/*设置关联值(Setter)
*  void objc_setAssociatedObject(id object, const void *key,id value,objc_AssociationPolicy)
*  object:与谁关联,通常都是传self
*  key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
*  value: 关联所设置的值
*  policy:内存管理策略,比如使用copy
*/
    objc_setAssociatedObject(self, &heightKey, @(height), OBJC_ASSOCIATION_ASSIGN);
}

-(double)height
{

/*获取关联值(Getter)
*  id objc_getAssociatedObject(id object, const void *key) *  object:与谁关联,通常都是传self *  key:唯一键,在设置关联时所使用的键 */ 
return [objc_getAssociatedObject(self, &heightKey) doubleValue]; 
}

 @end


原文地址:http://blog.csdn.net/three_bird/article/details/51525126?_biz=MjM5OTA1MDUyMA==&mid=407358558&idx=2&sn=b21877f23bf4063fa311185009c1f0b7&scene=0#wechat_redirect1464830913986?ref=myread
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值