Получить имя свойства в виде строки
Мне нужен способ передать свойство и получить присвоенное ему имя. Какие-либо предложения?
@property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return @"crazyObject"
9 ответов
Вы можете попробовать это:
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([self class], &propertyCount);
NSMutableArray * propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
NSLog(@"Names: %@", propertyNames);
Это так же просто, как это..., расширяя то, что Чак уже упоминал:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(@selector(prop))
#endif
Затем вы используете это так:
NSString *strProp = STR_PROP(myProperty);
Фон
Имейте в виду, что на самом деле свойства - это, по выражению Apple, "синтаксическая стенография для объявления методов доступа класса". Фактически само по себе объявление @property даже не работает. Ваш @synthesize
заявление переводит @property
в эквивалент двух методов:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Какой из них использовать, зависит от контекста, окружающего ваш self.crazyObject. (@synthesize
также создает соответствующую переменную экземпляра, если вы сами этого не сделали.) Результат всего этого заключается в том, что вы не можете реально преобразовать свойство в свойство и из него одним единственным методом.
Предложенное решение
Вы можете использовать то, что Apple уже предоставляет:
NSString *foo = NSStringFromSelector(@selector(myClassProperty));
Или сделать что-нибудь на заказ:
Учитывая, что self.crazyObject действительно переводит либо [self crazyObject]
или же [self setCrazyObject:foo]
к тому времени, когда ваш код будет запущен, вам, вероятно, понадобятся два метода, например:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
Затем вы можете захотеть по крайней мере 2 сопутствующих метода, таких как:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
В рамках этих методов вы можете использовать функции Foundation NSStringFromSelector
а также NSSelectorFromString
конвертировать туда и обратно между SEL и NSString. Используйте любые манипуляции со строками, которые вам нравятся, чтобы конвертировать туда и обратно между вашей сеттерной строкой (setCrazyObject) и именем вашего свойства (crazyObject).
Полное решение трудно предоставить, не зная точного варианта использования, но, надеюсь, это даст некоторые подсказки для тех, кто пытается достичь чего-то подобного. Возможно, даже некоторые полезные вещи станут возможными благодаря объединению этого подхода с ответом Оскара.
Вот функция, которая возвращает имя ивара, поэтому в основном она возвращает не только свойства, но и любой ивар класса. Я не нашел способа получить собственность напрямую, поэтому использовал трюк с иваром.
#import <objc/objc.h>
/// -----
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
После поиска и отладки я нахожу решение для меня...
добавленной #import <objc/runtime.h>
Методы object_getIvar(id obj, Ivar ivar) отправляют неправильный доступ и происходит сбой приложения. Я изменил некоторый код, и он работал отлично:
+(NSString*)stringWithProperty:(id)property withClass:(id)controller
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([controller class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([controller valueForKey:name] == property)
{
break;
}
}
free(ivars);
}
return name;
}
Модифицируя решение, оно работает, когда ваш объект уже выделен, иначе возвращает nil:-
NSString * NSStringFromProperty(NSObject* property, NSObject* class)
{
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([class class], &propertyCount);
NSString *name = nil;
for (unsigned int i = 0; i < propertyCount; ++i)
{
name = [NSString stringWithUTF8String:property_getName(properties[i])];
NSObject *object = [class valueForKey:name];
if (object != nil && object == property)
{
break;
}
else
{
name = nil;
}
}
free(properties);
return name;
}
Ты можешь использовать
NSString *str = NSStringFromSelector(@selector(crazyObject));
Хорошая вещь об этом подходе состоит в том, что:
- Xcode автоматически заполняет слово
crazyObject
для тебя. - Когда позже вы измените имя свойства с
crazyObject
вmyCrazyObject
, Xcode добавит предупреждающее высказывание"unrecognized selector!"
- довольно хорошо для отладки.
Я использую этот метод так часто, что даже создал функцию, которая позволяет писать меньше букв:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Теперь ваше окончательное решение может выглядеть так:
NSString *str = sfs(@selector(crazyObject));
Нетрадиционный, хакерский, некрасивый, поздний, но... с таким сильным именем, как он получает и работает как шарм:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"."] lastObject] componentsSeparatedByString:@" "] lastObject] stringByReplacingOccurrencesOfString:@"]" withString:@""] : @""
Пример использования:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname));
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate]));
...
Вы можете проверить мой подход в Gist, чтобы получить строку для свойства с автозаполнением и проверкой во время компиляции.
Как пользоваться:
Получить имя свойства для класса:
@interface AnyClass : NSObject
@property (strong) NSData *data;
@end
// == My approach ==
// C string for a class
PropertyNameForClass(AnyClass, data); // ==> "data"
// NSString for a class
PropertyStringForClass(AnyClass, data); // ==> @"data"
// Bad approach (no autocompletion; no compile-time check):
NSString *propertyName = @"data";
Получить имя свойства для протокола:
@protocol AnyProtocol
@property (strong) NSDate *date;
@end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> @"date"
Из Get name name as string, без использования библиотеки ссылок времени выполнения, просто определите:
#define propertyKeyPath(property) (@""#property)
#define propertyKeyPathLastComponent(property) [[(@""#property) componentsSeparatedByString:@"."] lastObject]
И тогда вы можете сделать что-то вроде этого:
NSLog(@"%@", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street