经过一段时间后,我想我已经为你想出了相当可扩展的解决方案。它所需要的只是使用以下帮助类创建对象,如下所示:
#import "DictionaryBackedObject.h"
extern NSString *const foo;
NSString *const foo = @"Foo";
@interface Foo : NSObject
@property NSString *foo;
@end
@implementation Foo
@end
int main() {
Foo *object = [DictionaryBackedObject dictionaryBackedObjectOfType:[Foo class]
backingDictionary:@{ foo: @"Bar" }
mutable:NO];
NSLog(@"%@", [object foo]);
}
注意:这个实现远非完美,它确实使用了“可怕的”dlsym API,也就是说,如果你想使用这个类,你不能从可执行文件中删除你的符号。此外,如果将其提交到应用商店,可能会导致拒绝。但是,如果您希望找到解决方法,还有其他方法可以自动确定要与字典一起使用的键。
此实现确实支持结构属性,以及弱、复制和原子属性。这将比在普通对象上设置属性慢得多,因为这是通过 Objective-c 的转发 API(支持结构返回所必需的)。
希望这对您有所帮助,我确实玩得很开心。
DictionaryBackedObject.h
@interface DictionaryBackedObject : NSObject
+(id) dictionaryBackedObjectOfType:(Class) kls backingDictionary:(NSDictionary *) dictionary mutable:(BOOL) isMutable;
@end
DictionaryBackedObject.m
#import "DictionaryBackedObject.h"
#include <stdalign.h>
#include <dlfcn.h>
@import ObjectiveC.runtime;
@import ObjectiveC.message;
__attribute__((noinline))
static SEL property_getGetterSelector(objc_property_t property) {
char *getter = property_copyAttributeValue(property, "G");
if (getter) {
SEL result = sel_registerName(getter);
free(getter);
return result;
}
return sel_registerName(property_getName(property));
}
__attribute__((noinline))
static SEL property_getSetterSelector(objc_property_t property) {
char *setter = property_copyAttributeValue(property, "S");
if (setter) {
SEL result = sel_registerName(setter);
free(setter);
return result;
}
char buffer[512];
char propertyName[512];
strncpy(propertyName, property_getName(property), 512);
propertyName[0] = toupper(propertyName[0]);
snprintf(buffer, 512, "set%s", propertyName);
return sel_registerName(buffer);
}
struct objc_property_attributes_t {
union {
struct {
int nonatomic : 1;
int copy : 1;
int weak : 1;
int strong : 1;
};
int memory_mode;
};
int is_readonly;
int is_dynamic;
};
static inline BOOL property_isAttributeNull(objc_property_t property, const char *attr) {
void *value = property_copyAttributeValue(property, attr);
BOOL results = value == NULL;
free(value);
return results;
}
static struct objc_property_attributes_t property_getPropertyAttributes(objc_property_t property) {
struct objc_property_attributes_t attrs;
attrs.nonatomic = !property_isAttributeNull(property, "N");
attrs.copy = !property_isAttributeNull(property, "C");
attrs.strong = attrs.copy || !property_isAttributeNull(property, "&");
attrs.weak = !property_isAttributeNull(property, "W");
attrs.is_readonly = !property_isAttributeNull(property, "R");
attrs.is_dynamic = !property_isAttributeNull(property, "D");
return attrs;
}
static objc_property_t class_getPropertyForSelector(Class kls, SEL cmd) {
#define VALID_PROPERTY(property) \
(property != NULL && (property_getGetterSelector(property) == cmd || property_getSetterSelector(property) == cmd))
const char *selName = sel_getName(cmd);
objc_property_t results = class_getProperty(kls, selName);
if (VALID_PROPERTY(results))
return results;
if (strstr(selName, "set") == selName) {
char lowercaseSel[512];
strncpy(lowercaseSel, strstr(selName, "set"), 512);
lowercaseSel[0] = tolower(lowercaseSel[0]);
results = class_getProperty(kls, lowercaseSel);
if (VALID_PROPERTY(results)) return results;
}
// Easy paths exhausted, go the 'hard' way of looping over all of the properties available
results = NULL;
unsigned propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(kls, &propertyCount);
for (unsigned propertyIndex = 0; propertyIndex < propertyCount; propertyIndex++) {
if (VALID_PROPERTY(properties[propertyIndex])) {
results = properties[propertyIndex];
break;
}
}
free(properties);
return results;
#undef VALID_PROPERTY
}
@implementation DictionaryBackedObject
-(id) initWithDictionary:(NSDictionary *) dictionary mutable:(BOOL) isMutable {
return nil;
}
+(Class) dictionaryBackedSubclassOfClass:(Class) kls {
@synchronized (kls) {
NSString *className = [NSStringFromClass(kls) stringByAppendingFormat:@"_dictionaryBacked"];
Class subclass = Nil;
if ((subclass = NSClassFromString(className))) {
return subclass;
}
subclass = objc_allocateClassPair(kls, [className UTF8String], 0);
class_addIvar(subclass, "_backingDictionary", sizeof(NSDictionary *), _Alignof(NSDictionary *), @encode(NSDictionary *));
class_addIvar(subclass, "_backingDictionaryIsMutable", sizeof(NSNumber *), _Alignof(NSNumber *), @encode(NSNumber *));
unsigned propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(kls, &propertyCount);
for (unsigned i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *type = property_copyAttributeValue(property, "T");
SEL getterSel = property_getGetterSelector(property);
SEL setterSel = property_getSetterSelector(property);
char getterTypeBuffer[512];
snprintf(getterTypeBuffer, 512, "%s@:", type);
char setterTypeBuffer[512];
snprintf(setterTypeBuffer, 512, "v@:%s", type);
NSUInteger typeSize;
NSUInteger typeAlignment;
NSGetSizeAndAlignment(type, &typeSize, &typeAlignment);
BOOL isStret = (typeSize * CHAR_BIT) > (WORD_BIT * 2);
class_addMethod(subclass, getterSel, isStret ? _objc_msgForward_stret : _objc_msgForward , getterTypeBuffer);
class_addMethod(subclass, setterSel, _objc_msgForward, setterTypeBuffer);
free(type);
}
free(properties);
Ivar backingDictionaryIvar = class_getInstanceVariable(subclass, "_backingDictionary");
Ivar backingDictionaryMutableIvar = class_getInstanceVariable(subclass, "_backingDictionaryIsMutable");
class_addMethod(subclass, @selector(forwardingTargetForSelector:), imp_implementationWithBlock(^id (id self) {
return nil;
}), "@@:");
class_addMethod(subclass, @selector(forwardInvocation:), imp_implementationWithBlock(^void (id self, NSInvocation *invocation) {
SEL _cmd = [invocation selector];
objc_property_t property = class_getPropertyForSelector([self class], _cmd);
if (property == NULL) {
[self doesNotRecognizeSelector:_cmd];
return;
}
BOOL isGetter = (_cmd == property_getGetterSelector(property));
struct objc_property_attributes_t attributes = property_getPropertyAttributes(property);
NSString *propertyType = (__bridge_transfer NSString *) CFStringCreateWithCStringNoCopy(
NULL, property_copyAttributeValue(property, "T"), kCFStringEncodingUTF8, NULL
);
NSUInteger propertySize;
NSGetSizeAndAlignment([propertyType UTF8String], &propertySize, NULL);
void *dlsymKey = dlsym(RTLD_MAIN_ONLY, property_getName(property));
id dictionaryKey = *(__unsafe_unretained id *) dlsymKey;
NSMutableDictionary *backingDictionary = object_getIvar(self, backingDictionaryIvar);
NSNumber *isMutable = object_getIvar(self, backingDictionaryMutableIvar);
// Performing synchronization on nil is a no-op, see objc_sync.mm:306.
@synchronized (attributes.nonatomic ? nil : self) {
if (isGetter) {
id value = backingDictionary[dictionaryKey];
if (attributes.strong) {
[invocation setReturnValue:&value];
} else if (attributes.weak) {
value = [value nonretainedObjectValue];
[invocation setReturnValue:&value];
} else {
void *buffer = alloca(propertySize);
[value getValue:buffer];
[invocation setReturnValue:buffer];
}
} else {
if ((attributes.is_readonly || ![isMutable boolValue])) {
[self doesNotRecognizeSelector:_cmd];
return;
}
id dictionaryValue = nil;
void *newValue = alloca(propertySize);
[invocation getArgument:newValue atIndex:2];
if (attributes.strong) {
dictionaryValue = (__bridge id) newValue;
if (attributes.copy) {
dictionaryValue = [dictionaryValue copy];
}
} else if (attributes.weak) {
dictionaryValue = [NSValue valueWithNonretainedObject:(__bridge id) newValue];
} else {
dictionaryValue = [NSValue valueWithBytes:newValue objCType:[propertyType UTF8String]];
}
if (dictionaryValue == nil) {
[backingDictionary removeObjectForKey:dictionaryKey];
} else {
[backingDictionary setObject:dictionaryValue forKey:dictionaryKey];
}
}
}
}), "v@:@");
class_addMethod(subclass, @selector(initWithDictionary:mutable:), imp_implementationWithBlock(^id (id self, NSDictionary *dictionary, BOOL mutable) {
object_setIvar(self, backingDictionaryIvar, dictionary);
object_setIvar(self, backingDictionaryMutableIvar, @(mutable));
return self;
}), "@@:@c");
objc_registerClassPair(subclass);
return subclass;
}
}
+(id) dictionaryBackedObjectOfType:(Class)kls backingDictionary:(NSDictionary *)dictionary mutable:(BOOL)isMutable {
Class subclass = [self dictionaryBackedSubclassOfClass:kls];
return [[subclass alloc] initWithDictionary:dictionary mutable:isMutable];
}
@end