192 lines
6.7 KiB
Objective-C
192 lines
6.7 KiB
Objective-C
//
|
|
// FSUserDefaults.h
|
|
//
|
|
// Created by Christian Floisand.
|
|
// Copyright © 2017 Flyingsand. All rights reserved.
|
|
//
|
|
|
|
#import "FSUserDefaults.h"
|
|
#import <objc/runtime.h>
|
|
#import <objc/message.h>
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wstrlcpy-strlcat-size"
|
|
|
|
#define TYPE_ENCODING_STRING_SETTER "v@:_"
|
|
#define TYPE_ENCODING_STRING_GETTER "_@:"
|
|
|
|
#define SELECTOR_SETTER_BOOL "setBool:forKey:"
|
|
#define SELECTOR_SETTER_int "setInteger:forKey:"
|
|
#define SELECTOR_SETTER_float "setFloat:forKey:"
|
|
#define SELECTOR_SETTER_double "setDouble:forKey:"
|
|
#define SELECTOR_SETTER_id "setObject:forKey:"
|
|
#define SELECTOR_SETTER_NSURL "setURL:forKey:"
|
|
|
|
#define SELECTOR_GETTER_BOOL "boolForKey:"
|
|
#define SELECTOR_GETTER_int "integerForKey:"
|
|
#define SELECTOR_GETTER_float "floatForKey:"
|
|
#define SELECTOR_GETTER_double "doubleForKey:"
|
|
#define SELECTOR_GETTER_id "objectForKey:"
|
|
#define SELECTOR_GETTER_NSURL "URLForKey:"
|
|
|
|
#define SELECTOR_STRING_SETTER(type) SELECTOR_SETTER_ ## type
|
|
#define SELECTOR_STRING_GETTER(type) SELECTOR_GETTER_ ## type
|
|
|
|
#define METHOD_NAME_SETTER(type) type ## Setter
|
|
#define METHOD_NAME_GETTER(type) type ## Getter
|
|
|
|
static const char *
|
|
CopyPropertyNameFromSetter(const char *setterName) {
|
|
char *propertyName = malloc(sizeof(char) * (strlen(setterName) - 3));
|
|
strlcpy(propertyName, setterName + 3, strlen(setterName) - 3);
|
|
propertyName[0] = tolower(propertyName[0]);
|
|
return propertyName;
|
|
}
|
|
|
|
static bool
|
|
IsPropertyURL(const char *attr) {
|
|
attr += 3;
|
|
static const char *URLClassName = "NSURL";
|
|
size_t compareLen = MIN(strlen(attr), strlen(URLClassName));
|
|
bool isUrl = strncmp(attr, URLClassName, compareLen) == 0;
|
|
|
|
return isUrl;
|
|
}
|
|
|
|
#define METHOD_IMPLEMENTATION_SETTER(tname, type) void METHOD_NAME_SETTER(tname)(id self, SEL _cmd, type value) { \
|
|
const char *selectorString = SELECTOR_STRING_SETTER(tname); \
|
|
SEL setterSelector = sel_registerName(selectorString); \
|
|
const char *propertyName = CopyPropertyNameFromSetter(sel_getName(_cmd)); \
|
|
NSCAssert([self isKindOfClass:[FSUserDefaults class]], @""); \
|
|
NSUserDefaults *userDefaults = ((FSUserDefaults *)self).userDefaults; \
|
|
((void(*)(id,SEL,type,id))objc_msgSend)(userDefaults, setterSelector, value, [NSString stringWithUTF8String:propertyName]); \
|
|
free((void *)propertyName); \
|
|
}
|
|
|
|
#define METHOD_IMPLEMENTATION_GETTER(tname, type) type METHOD_NAME_GETTER(tname)(id self, SEL _cmd) { \
|
|
const char *selectorString = SELECTOR_STRING_GETTER(tname); \
|
|
SEL getterSelector = sel_registerName(selectorString); \
|
|
NSCAssert([self isKindOfClass:[FSUserDefaults class]], @""); \
|
|
NSUserDefaults *userDefaults = ((FSUserDefaults *)self).userDefaults; \
|
|
return ((type(*)(id,SEL,id))objc_msgSend)(userDefaults, getterSelector, [NSString stringWithUTF8String:sel_getName(_cmd)]); \
|
|
}
|
|
|
|
#define GENERATE_SETTER_GETTER_METHOD_PAIR(tname, type) \
|
|
METHOD_IMPLEMENTATION_SETTER(tname, type) \
|
|
METHOD_IMPLEMENTATION_GETTER(tname, type)
|
|
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(BOOL, BOOL)
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(int, int)
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(float, float)
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(double, double)
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(id, id)
|
|
GENERATE_SETTER_GETTER_METHOD_PAIR(NSURL, NSURL*)
|
|
|
|
|
|
@implementation FSUserDefaults
|
|
|
|
+ (instancetype)sharedInstance {
|
|
static id _instance = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
_instance = [self new];
|
|
((FSUserDefaults *)_instance)->_userDefaults = [NSUserDefaults standardUserDefaults];
|
|
});
|
|
return _instance;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
return (self = [self initWithSuiteName:nil]);
|
|
}
|
|
|
|
- (instancetype)initWithSuiteName:(NSString *)suiteName {
|
|
self = [super init];
|
|
if (self) {
|
|
_userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (BOOL)resolveInstanceMethod:(SEL)sel {
|
|
const char *selectorName = sel_getName(sel);
|
|
const char *propertyName = selectorName;
|
|
BOOL isSetter = NO;
|
|
|
|
if ((strlen(selectorName) > 3) && (strncmp("set", selectorName, 3) == 0)) {
|
|
propertyName = CopyPropertyNameFromSetter(selectorName);
|
|
isSetter = YES;
|
|
}
|
|
|
|
objc_property_t property = class_getProperty(self, propertyName);
|
|
if (isSetter) {
|
|
free((void *)propertyName);
|
|
}
|
|
|
|
if (property != NULL) {
|
|
const char *propertyAttrs = property_getAttributes(property);
|
|
char propertyType = propertyAttrs[1];
|
|
|
|
const char *methodTypeString;
|
|
int index;
|
|
if (isSetter) {
|
|
methodTypeString = TYPE_ENCODING_STRING_SETTER;
|
|
index = 3;
|
|
} else {
|
|
methodTypeString = TYPE_ENCODING_STRING_GETTER;
|
|
index = 0;
|
|
}
|
|
|
|
char *typeEncodingString = malloc(sizeof(char) * (strlen(methodTypeString) + 1));
|
|
strlcpy(typeEncodingString, methodTypeString, strlen(methodTypeString) + 1);
|
|
typeEncodingString[index] = propertyType;
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wpointer-type-mismatch"
|
|
IMP methodImpl;
|
|
switch (propertyType) {
|
|
case 'c':
|
|
case 'B':
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(BOOL) : METHOD_NAME_GETTER(BOOL));
|
|
break;
|
|
case 's':
|
|
case 'i':
|
|
case 'l':
|
|
case 'q':
|
|
case 'S':
|
|
case 'I':
|
|
case 'L':
|
|
case 'Q':
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(int) : METHOD_NAME_GETTER(int));
|
|
break;
|
|
case 'f':
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(float) : METHOD_NAME_GETTER(float));
|
|
break;
|
|
case 'd':
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(double) : METHOD_NAME_GETTER(double));
|
|
break;
|
|
case '@':
|
|
if (IsPropertyURL(propertyAttrs)) {
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(NSURL) : METHOD_NAME_GETTER(NSURL));
|
|
} else {
|
|
methodImpl = (IMP)(isSetter ? METHOD_NAME_SETTER(id) : METHOD_NAME_GETTER(id));
|
|
}
|
|
break;
|
|
default:
|
|
NSAssert(NO, @"[FSUserDefaults] Unhandled property type.");
|
|
break;
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
class_addMethod(self, sel, methodImpl, typeEncodingString);
|
|
free(typeEncodingString);
|
|
|
|
return YES;
|
|
}
|
|
|
|
return [super resolveInstanceMethod:sel];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma clang diagnostic pop
|