我有一个类,它在字典中保存了很好定义键的属性。我想用一个类替换这个属性字典,让它称之为AttributeSet。有定义键的地方:
extern NSString *const Foo;
我想拥有属性:
@interface AttributeSet : NSObject
@property(strong) NSString *Foo;
...a ton more
@end
我实际上希望AttributeSet对象在幕后使用字典,因为出于向后兼容性的原因。所以当发生这种情况时:
attributeSet.Foo = @"bar";
我实际上希望这种情况发生:
- (void)setFoo:(NSString *)foo {
self.attributes[Foo] = foo; //Foo is the extern variable Foo
}
但我不想为所有属性定义getter和setter。
我知道我可以使用键值观察但是1)要求我有一个(属性名称)@" Foo" - > (变量名)Foo和2)导致两者属性被设置和设置的字典值实际上我只想设置字典。
我知道我可以这样做:https://github.com/iosptl/ios6ptl/blob/master/ch28/Person/Person/Person.m 但那会1)仍然要求我有一个映射,2)要求我为每个属性都有一个@dynamic。
有更自动的方法吗?
由于
答案 0 :(得分:1)
要使用动态生成的访问器方法,如您链接的Person
代码所示,无需@dynamic
,您可以在类的类别中声明属性,而不是类本身:
@interface AttributeSet : NSObject
// ... no properties here ...
@end
@interface AttributeSet (YourPropertiesCategoryName)
@property(strong) NSString *Foo;
...a ton more
@end
编译器将自动合成在类本身或类扩展中声明的属性(类似于没有类别名称的类别),但不适用于类别。
请注意,您不需要也不应该为该类别提供实施。 (如果你这样做,编译器会抱怨属性缺乏实现。它不会自动合成它们,但是你仍然需要使用@dynamic
来消除警告。)
答案 1 :(得分:1)
经过一段时间后,我想我已经为您提出了相当可扩展的解决方案。您需要的只是使用以下帮助程序类创建对象,如下所示:
#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,这意味着,如果您希望使用此类,则无法从可执行文件中删除符号。此外,如果将其提交到应用商店,可能会导致拒绝。如果您希望找到解决方法,还有其他方法可以自动确定要与字典一起使用的密钥。
此实现确实支持struct属性,以及弱,复制和原子属性。它将比在普通对象上设置属性要慢得多,因为这会通过objective-c的转发API(支持struct返回所需)。
希望这可以帮助你,我当然有很多乐趣。
<强> 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
答案 2 :(得分:0)
Rob Napier的例子是一个不错的选择;编译器会为你生成访问器,除非你不告诉它,以及你用@dynamic
指令告诉它的方式。
另一种选择是自动代码生成:编写脚本为您的setter发出ObjC代码。
我能想到的第三个是overwriting the accessors during runtime。在您的班级+initialize
中,您可以从运行时库中获取其属性列表,并使用class_replaceMethod()
插入您自己的使用词典而非ivars的访问者。这将需要一些字符串修改来获取彼此的访问者名称和密钥。
这里有一个关于最后一个选项演示的要点:https://gist.github.com/woolsweater/4fb874b15449ee7fd7e8