RunTime给NSUserDefaults添加属性

原文地址、GitHub Demo

一、为什么要给NSUserDefaults添加属性?

平时开发中或多或少都会用到NSUserDefaults用来存储一些轻量级的数据,而平时使用NSUserDefaults都是这样的:

// 存储
[[NSUserDefaults standardUserDefaults] setObject:@"value" forKey:@"key"];
// 读取
NSString *value = [[NSUserDefaults standardUserDefaults] objectForKey:@"key"];

有没有哪一个瞬间觉得这样其实有点麻烦呢?

比如当我们使用归档存储用户数据模型之后,解档读取模型的值的时候我们一般是这样操作的:

NSString *nickName = [UserModel share].nickName;
// 可能为了方便书写,也会将[UserModel share]来个宏定义
#define UserData [UserModel share]
NSString *nickName = UserData.nickName;

代码书写起来,直接调用OC方法肯定是不如点语法来的方便的,那么有没有可能将NSUserDefaults搞成UserModel这种写法呢?

二、给NSUserDefaults添加分类属性

我们肯定马上就会想到给NSUserDefaults添加一个分类MSCategory

#import 

@interface NSUserDefaults (MSCategory)

@property (nonatomic, copy) NSString *key;

@end
#import "NSUserDefaults+MSCategory.h"

@implementation NSUserDefaults (MSCategory)

- (void)setKey:(NSString *)key {
    [[NSUserDefaults standardUserDefaults] setObject:key forKey:@"key"];
}

- (NSString *)key {
    return [[NSUserDefaults standardUserDefaults] objectForKey:@"key"];
}

@end

这样就可以通过点语法直接完成存储和读取了,例如:

// 存储
[NSUserDefaults standardUserDefaults].key = @"value";
// 读取
NSString *value = [NSUserDefaults standardUserDefaults].key;

三、使用RunTime动态添加属性实现一劳永逸

当我们给NSUserDefaults添加了分类之后,发现一个问题,每当我添加一个属性之后,我都要在.m文件中去实现相应的set和get方法才行,有没有可能,我们不用管.m中的代码,只要在.h中添加属性,存储和读取都能正常执行呢?

这个时候RunTime就可以派上用场了,我们知道,OC运行时提供了添加方法、交换方法、替换方法等函数。这里用到的就是添加方法函数class_addMethod

将下面这段代码拷贝到NSUserDefaults的分类.m文件中即可实现一劳永逸

#import "NSUserDefaults+MSCategory.h"
#import 

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-property-implementation"

@implementation NSUserDefaults (MSCategory)

static NSString * prefix = @"ms_";// 前缀

+ (void)load {
    NSArray *arr = @[@"TB,N",//bool
                     @"TC,N",//unsigned char
                     @"Tc,N",//char
                     @"TS,N",//unsigned short
                     @"Ts,N",//short
                     @"TI,N",//unsigned int
                     @"Ti,N",//int
                     @"TL,N",//unsigned long
                     @"Tl,N",//long
                     @"TQ,N",//unsigned long long
                     @"Tq,N",//long long
                     @"Tf,N",//float
                     @"Td,N",//double
                     @"T@\"NSURL\",&,N",
                     @"T@\"NSData\",&,N",
                     @"T@\"NSDate\",&,N",
                     @"T@\"NSString\",&,N",
                     @"T@\"NSNumber\",&,N",
                     @"T@\"NSArray\",&,N",
                     @"T@\"NSDictionary\",&,N"];
    u_int count;
    // 属性列表
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i++) {
        // get方法名
        NSString *getSel = [NSString stringWithUTF8String:property_getName(properties[i])];
        NSString *first = [getSel substringToIndex:1];
        NSAssert([first isEqualToString:first.lowercaseString], @"属性名首字母不能大写");
        // 将首字母大写
        NSString *uppercase = [NSString stringWithFormat:@"%@%@", [[getSel substringToIndex:1] uppercaseString], [getSel substringFromIndex:1]];
        // set方法名
        NSString *setSel = [NSString stringWithFormat:@"set%@:", uppercase];
        // 属性类型
        NSString *attribute = [NSString stringWithUTF8String:property_getAttributes(properties[i])];
        // 检查NSUserDefaults是否可以存储这个类型
        NSAssert([arr containsObject:attribute], @"属性列表中存在不能存储的类型");
        
        NSString *code = [attribute substringWithRange:NSMakeRange(1, 1)];
        const char * save_types = [[NSString stringWithFormat:@"V@:%@", code] UTF8String];
        const char * read_types = [[NSString stringWithFormat:@"%@@:", code] UTF8String];
        IMP save_IMP,read_IMP;
        if ([attribute isEqualToString:@"T@\"NSURL\",&,N"]) {
            save_IMP = (IMP)ms_saveURL;
            read_IMP = (IMP)ms_readURL;
        } else if ([code isEqualToString:@"@"]) {
            save_IMP = (IMP)ms_saveObjC;
            read_IMP = (IMP)ms_readObjC;
        } else if ([code isEqualToString:@"d"]) {
            save_IMP = (IMP)ms_saveDouble;
            read_IMP = (IMP)ms_readDouble;
        } else if ([code isEqualToString:@"f"]) {
            save_IMP = (IMP)ms_saveFloat;
            read_IMP = (IMP)ms_readFloat;
        } else if ([code isEqualToString:@"q"] || [code isEqualToString:@"l"]) {
            save_IMP = (IMP)ms_saveLong;
            read_IMP = (IMP)ms_readLong;
        } else if ([code isEqualToString:@"Q"] || [code isEqualToString:@"L"]) {
            save_IMP = (IMP)ms_saveUnsignedLong;
            read_IMP = (IMP)ms_readUnsignedLong;
        } else if ([code isEqualToString:@"I"]) {
            save_IMP = (IMP)ms_saveUnsignedInt;
            read_IMP = (IMP)ms_readUnsignedInt;
        } else {
            save_IMP = (IMP)ms_saveInt;
            read_IMP = (IMP)ms_readInt;
        }
        bool save = class_addMethod([self class], NSSelectorFromString(setSel), save_IMP, save_types);
        bool read = class_addMethod([self class], NSSelectorFromString(getSel), read_IMP, read_types);
        NSAssert(save&&read, @"添加方法失败");
    }
    free(properties);
}

#pragma mark - NSURL
static void ms_saveURL(id self, SEL _cmd, NSURL *obj) {
    [[NSUserDefaults standardUserDefaults] setURL:obj forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static NSURL * ms_readURL(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[NSUserDefaults standardUserDefaults] URLForKey:key];
}

#pragma mark - 其他OC类型
static void ms_saveObjC(id self, SEL _cmd, id obj) {
    [[NSUserDefaults standardUserDefaults] setObject:obj forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static id ms_readObjC(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}

#pragma mark - bool,char,short,int
static void ms_saveInt(id self, SEL _cmd, int value) {
    [[NSUserDefaults standardUserDefaults] setObject:@(value) forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static int ms_readInt(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[[NSUserDefaults standardUserDefaults] objectForKey:key] intValue];
}

#pragma mark - unsigned int
static void ms_saveUnsignedInt(id self, SEL _cmd, unsigned int value) {
    [[NSUserDefaults standardUserDefaults] setObject:@(value) forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static unsigned int ms_readUnsignedInt(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[[NSUserDefaults standardUserDefaults] objectForKey:key] unsignedIntValue];
}

#pragma mark - long
static void ms_saveLong(id self, SEL _cmd, long value) {
    [[NSUserDefaults standardUserDefaults] setObject:@(value) forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static long ms_readLong(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[[NSUserDefaults standardUserDefaults] objectForKey:key] longValue];
}

#pragma mark - unsigned long
static void ms_saveUnsignedLong(id self, SEL _cmd, unsigned long value) {
    [[NSUserDefaults standardUserDefaults] setObject:@(value) forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static unsigned long ms_readUnsignedLong(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[[NSUserDefaults standardUserDefaults] objectForKey:key] unsignedLongValue];
}

#pragma mark - float
static void ms_saveFloat(id self, SEL _cmd, float value) {
    [[NSUserDefaults standardUserDefaults] setFloat:value forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static float ms_readFloat(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[NSUserDefaults standardUserDefaults] floatForKey:key];
}

#pragma mark - double
static void ms_saveDouble(id self, SEL _cmd, double value) {
    [[NSUserDefaults standardUserDefaults] setDouble:value forKey:GetKeyWithSelector(_cmd)];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
static double ms_readDouble(id self, SEL _cmd) {
    NSString *key = [NSString stringWithFormat:@"%@%@", prefix, NSStringFromSelector(_cmd)];
    return [[NSUserDefaults standardUserDefaults] doubleForKey:key];
}

#pragma mark - 通过set方法名获取键名
static NSString * GetKeyWithSelector(SEL sel) {
    NSString *name = [NSStringFromSelector(sel) substringWithRange:NSMakeRange(3, NSStringFromSelector(sel).length - 4)];
    name = [NSString stringWithFormat:@"%@%@", [[name substringToIndex:1] lowercaseString], [name substringFromIndex:1]];
    return [NSString stringWithFormat:@"%@%@", prefix, name];
}

@end
#pragma clang diagnostic pop

你可能感兴趣的:(RunTime给NSUserDefaults添加属性)