runtime获得所有属性进行解档,归档,description

在写一个归档解档的,其实原理都一样,用 runtime 获取当前类的所有属性名和属性值,进行下一步操作.


- (void)encodeWithCoder:(NSCoder *)aCoder {

    unsigned int outCount = 0;

    Ivar *ivars = class_copyIvarList([self class], &outCount);

    

    for (unsigned int i = 0; i < outCount; ++i) {

        Ivar ivar = ivars[i];

        

        // 获取成员变量名

        const void *name = ivar_getName(ivar);

        NSString *ivarName = [NSString stringWithUTF8String:name];

        // 去掉成员变量的下划线

        ivarName = [ivarName substringFromIndex:1];

        

        // 获取getter方法

        SEL getter = NSSelectorFromString(ivarName);

        if ([self respondsToSelector:getter]) {

            const void *typeEncoding = ivar_getTypeEncoding(ivar);

            NSString *type = [NSString stringWithUTF8String:typeEncoding];

            

            // const void *

            if ([type isEqualToString:@"r^v"]) {

                const char *value = ((const void *(*)(id, SEL))(void *)objc_msgSend)((id)self, getter);

                NSString *utf8Value = [NSString stringWithUTF8String:value];

                [aCoder encodeObject:utf8Value forKey:ivarName];

                continue;

            }

            // int

            else if ([type isEqualToString:@"i"]) {

                int value = ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);

                [aCoder encodeObject:@(value) forKey:ivarName];

                continue;

            }

            // float

            else if ([type isEqualToString:@"f"]) {

                float value = ((float (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);

                [aCoder encodeObject:@(value) forKey:ivarName];

                continue;

            }

            

            id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);

            if (value != nil && [value respondsToSelector:@selector(encodeWithCoder:)]) {

                [aCoder encodeObject:value forKey:ivarName];

            }

        }

    }

    

    free(ivars);

    

}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {

    

    if (self = [super init]) {

        unsigned int outCount = 0;

        Ivar *ivars = class_copyIvarList([self class], &outCount);

        

        for (unsigned int i = 0; i < outCount; ++i) {

            Ivar ivar = ivars[i];

            

            // 获取成员变量名

            const void *name = ivar_getName(ivar);

            NSString *ivarName = [NSString stringWithUTF8String:name];

            // 去掉成员变量的下划线

            ivarName = [ivarName substringFromIndex:1];

            // 生成setter格式

            NSString *setterName = ivarName;

            // 那么一定是字母开头

            if (![setterName hasPrefix:@"_"]) {

                NSString *firstLetter = [NSString stringWithFormat:@"%c", [setterName characterAtIndex:0]];

                setterName = [setterName substringFromIndex:1];

                setterName = [NSString stringWithFormat:@"%@%@", firstLetter.uppercaseString, setterName];

            }

            setterName = [NSString stringWithFormat:@"set%@:", setterName];

            // 获取getter方法

            SEL setter = NSSelectorFromString(setterName);

            if ([self respondsToSelector:setter]) {

                const void *typeEncoding = ivar_getTypeEncoding(ivar);

                NSString *type = [NSString stringWithUTF8String:typeEncoding];

                NSLog(@"%@", type);

                

                // const void *

                if ([type isEqualToString:@"r^v"]) {

                    NSString *value = [aDecoder decodeObjectForKey:ivarName];

                    if (value) {

                        ((void (*)(id, SEL, const void *))objc_msgSend)(self, setter, value.UTF8String);

                    }

                    

                    continue;

                }

                // int

                else if ([type isEqualToString:@"i"]) {

                    NSNumber *value = [aDecoder decodeObjectForKey:ivarName];

                    if (value != nil) {

                        ((void (*)(id, SEL, int))objc_msgSend)(self, setter, [value intValue]);

                    }

                    continue;

                } else if ([type isEqualToString:@"f"]) {

                    NSNumber *value = [aDecoder decodeObjectForKey:ivarName];

                    if (value != nil) {

                        ((void (*)(id, SEL, float))objc_msgSend)(self, setter, [value floatValue]);

                    }

                    continue;

                }

                

                // object

                id value = [aDecoder decodeObjectForKey:ivarName];

                if (value != nil) {

                    ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);

                }

            }

        }

        

        free(ivars);

    }

    return self;

}



description

在开发过程中, 往往会有很多的model来装载属性. 而在开发期间经常会进行调试查看model里的属性值是否正确. 那么问题来了, 在objective-c里使用NSLog("%@",model)这行代码打印出来的却是model的地址. 不是我们所想要的结果~! 看图:


那么问题又来了?有没有办法解决这个问题尼,答案那就是有~!只需要重写- (NSString *)description方法即可。如下代码:

.h文件

#import <Foundation/Foundation.h>

@interface TestModel : NSObject
@property (copy,nonatomic) NSString *text;
@property (assign,nonatomic) NSInteger index;
@end

.m文件

#import "TestModel.h"

@implementation TestModel
- (NSString *)description {
    return [NSString stringWithFormat:@"text:%@--index:%zi",self.text,self.index];
}
@end

然后这时候在使用NSLog("%@",model)这行代码就能打印我们想要的结果了。 看如下图:


那么问题继续来了...
如果model里有N多个属性尼, 可能10个, 可能20个... 难道要在description方法里一个一个写属性并拼接返回? 你不嫌麻烦, 我光看着都蛋疼了... 所以我们可以采用runtime技术来动态获取属性并返回. 如下修改后的.m文件代码:

修改后的.m文件

#import "TestModel.h"
#import <objc/runtime.h>//导入runtime头文件

@implementation TestModel
- (NSString *)description {
    //初始化一个字典
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    //得到当前class的所有属性
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    //循环并用KVC得到每个属性的值
    for (int i = 0; i<count; i++) {
        objc_property_t property = properties[i];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@"nil";//默认值为nil字符串
        [dictionary setObject:value forKey:name];//装载到字典里
    }

    //释放
    free(properties);

    //return
    return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,dictionary];
}
@end

然后在打印model, 如下图:


这里写图片描述

debugDescription

现在问题继续来了..
在项目中NSLog语句往往也很多. 如果重写description方法. 在控制台则会打印出很多属性. 看着就不舒服~~而且还有一个问题就是, 有时候我们其实并不需要打印model的属性.. 那这样重写description方法反而适得其反了! 所有, 现在有一个解决方案就是重写debugDescription方法


什么是debugDescription? 其实debugDescriptiondescription是一样的效果. 只不过唯一的区别就是debugDescription是在Xcode控制台里使用po命令的时候调用的~!


debugDescription的实现其实也就是调用了description方法而已

so, 在开发过程中并且model调试的时候, 笔者推荐重写debugDescription方法而不是重写description方法. 当需要打印model的属性的时候, 在控制台里使用po命令即可. 如下在此修改后的.m文件

#import "TestModel.h"
#import <objc/runtime.h>//导入runtime头文件

@implementation TestModel

// 重写debugDescription, 而不是description
- (NSString *)debugDescription {
    //声明一个字典
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    //得到当前class的所有属性
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    //循环并用KVC得到每个属性的值
    for (int i = 0; i<count; i++) {
        objc_property_t property = properties[i];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@"nil";//默认值为nil字符串
        [dictionary setObject:value forKey:name];//装载到字典里
    }

    //释放
    free(properties);

    //return
    return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,dictionary];
}
@end

看如下图, 分别使用了NSLogpo命令的打印



这里写图片描述

结果:


这里写图片描述

这就达到了我们想要的效果, 如果需要打印model的属性, 打个断点然后使用po命令即可

demo地址

最后,附上本文章的一个小demo示例代码,已放在github上。
https://github.com/DemoMania/DebugDescriptionDemo

总结

  • model调试的时候, 推荐重写debugDescription而不是description
  • 利用runtime技术动态获取class的属性
  • base基类里重写debugDescription方法,随后所有model继承与baseModel即可。
  • 在重写的debugDescription的方法里最好不要调用自身debugDescription([self debugDescription])





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值