Написание собственных @динамических свойств в Cocoa
Предположим (ради аргумента), что у меня есть класс вида, который содержит NSDictionary. Я хочу целую кучу свойств, все из которых получают доступ к членам этого словаря.
Например, я хочу @property NSString* title
и @property NSString* author
.
Для каждого из этих свойств реализация одинакова: для getter, call [dictionary objectForKey:propertyName];
и для setter делать то же самое с setObject: forKey:.
Для загрузки всех этих методов потребуется много времени и использовать множество кода копирования и вставки. Есть ли способ генерировать их все автоматически, например, Core Data с @dynamic свойствами для подклассов NSManagedObject? Чтобы быть ясным, я хочу только этого средства доступа для свойств, которые я определяю в заголовке, а не только любой произвольный ключ.
Я столкнулся с valueForUndefinedKey: как часть кодирования ключевых значений, которые могут обрабатывать геттеры, но я не совсем уверен, что это лучший способ пойти.
Мне нужно, чтобы они были явными свойствами, поэтому я могу привязываться к ним в Interface Builder: в конечном итоге я планирую написать палитру IB для этого представления.
(Кстати, я знаю, что мой пример использования NSDictionary для их хранения немного надуман. Я на самом деле пишу подкласс WebView, и свойства будут ссылаться на идентификаторы HTML-элементов, но это не важно для логики моего вопроса!)
Ответы
Ответ 1
Мне удалось решить эту проблему самостоятельно после того, как вы пролили objective-c документацию по времени выполнения.
Я реализовал этот метод класса:
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
NSString *method = NSStringFromSelector(aSEL);
if ([method hasPrefix:@"set"])
{
class_addMethod([self class], aSEL, (IMP) accessorSetter, "[email protected]:@");
return YES;
}
else
{
class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
Далее следуют две функции C:
NSString* accessorGetter(id self, SEL _cmd)
{
NSString *method = NSStringFromSelector(_cmd);
// Return the value of whatever key based on the method name
}
void accessorSetter(id self, SEL _cmd, NSString* newValue)
{
NSString *method = NSStringFromSelector(_cmd);
// remove set
NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""];
// Set value of the key anID to newValue
}
Поскольку этот код пытается реализовать любой метод, который вызывается в классе и не был реализован, это вызовет проблемы, если кто-то попытается называть то, что вы ожидаете. Я планирую добавить проверку здравомыслия, чтобы убедиться, что имена совпадают с тем, что я ожидаю.
Ответ 2
Вы можете использовать сочетание предлагаемых вами вариантов:
- используйте ключевое слово @dynamic
- переписать valueForKey: и setValue: forKey: доступ к словарю
- используйте API-интерфейс отражения objective-c class_getProperty и проверьте его на nil. Если это не так, ваш класс обладает таким свойством. Это не так.
- затем вызовите супер метод в тех случаях, когда такое свойство не существует.
Надеюсь, это поможет. Может показаться немного взломанным (используя отражение), но на самом деле это очень гибкое, а также абсолютно "законное" решение проблемы...
PS: возможен путь coredata, но в вашем случае будет полный избыток...
Ответ 3
Подружитесь с макросом? Это не может быть на 100% правильным.
#define propertyForKey(key, type) \
- (void) set##key: (type) key; \
- (type) key;
#define synthesizeForKey(key, type) \
- (void) set##key: (type) key \
{ \
[dictionary setObject];// or whatever \
} \
- (type) key { return [dictionary objectForKey: key]; }
Ответ 4
звучит так, как будто вы должны использовать класс вместо словаря. вы приближаетесь к тому, чтобы вручную реализовать то, что язык пытается вам дать.
Ответ 5
Есть хороший блог с примером кода с более надежными проверками динамических свойств на https://tobias-kraentzer.de/2013/05/15/dynamic-properties-in-objective-c/ также очень хороший ответ SO в Objective-C динамические свойства во время выполнения?.
Несколько точек ответа. Возможно, хотите объявить @property в интерфейсе, чтобы позволить typeahead также объявлять свойства динамическими в реализации.