J'ai besoin d'un moyen de transmettre une propriété et d'obtenir le nom qui lui est attribué. Aucune suggestion?
@property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return @"crazyObject"
Vous pouvez essayer ceci:
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);
C'est aussi simple que cela… développant ce que Chuck a déjà mentionné:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(@selector(prop))
#endif
Vous l'utilisez alors comme ceci:
NSString *strProp = STR_PROP(myProperty);
Contexte
N'oubliez pas que les propriétés ne sont en réalité que, pour quote Apple , "un raccourci syntaxique pour déclarer les méthodes d'accès d'une classe". En fait, par elle-même, la déclaration @property ne fonctionne même pas. Votre instruction @synthesize
traduit le @property
en l'équivalent de deux méthodes:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Lequel est utilisé dépend du contexte entourant votre self.crazyObject. (@synthesize
crée également une variable d'instance correspondante si vous ne le faites pas vous-même.) Tout cela a pour conséquence que vous ne pouvez pas vraiment traduire vers et à partir d'une propriété avec une seule méthode.
Solution proposée
Vous pouvez utiliser ce que Apple fournit déjà:
NSString *foo = NSStringFromSelector(@selector(myClassProperty));
Ou faire quelque chose de personnalisé:
Étant donné que self.crazyObject se traduit réellement par [self crazyObject]
ou [self setCrazyObject:foo]
au moment de l’exécution de votre code, vous aurez probablement besoin de deux méthodes, telles que:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
Vous voudrez peut-être au moins 2 méthodes compagnes, telles que:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
Dans ces méthodes, vous pouvez utiliser les fonctions de baseNSStringFromSelector
et NSSelectorFromString
pour effectuer des conversions entre SEL et NSString. Utilisez les manipulations de chaîne que vous souhaitez convertir entre votre chaîne de définition (setCrazyObject) et le nom de votre propriété (crazyObject).
Il est difficile de fournir une solution complète sans connaître le cas d'utilisation exact, mais nous espérons que cela fournira quelques indices supplémentaires à quiconque tentera de réaliser quelque chose de similaire. Il pourrait même être utile de combiner cette approche avec la réponse d’Oscar pour rendre utiles certaines choses utiles.
Voici une fonction qui retourne le nom d'un ivar, donc fondamentalement, elle ne retourne pas seulement les propriétés mais n'importe quel ivar de la classe. Je n'ai pas trouvé le moyen d'obtenir la propriété directement, alors j'ai utilisé l'astuce Ivar.
#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;
}
Après la recherche et le débogage, je trouve une solution pour moi ...
#import <objc/runtime.h>
ajouté
Les méthodes object_getIvar (id obj, Ivar ivar) envoient un mauvais accès et bloquent l'application. Je modifie du code et cela a très bien fonctionné:
+(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;
}
En modifiant la solution, cela fonctionne quand votre objet est déjà alloué, sinon il retourne 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;
}
De Obtenir le nom de la propriété sous forme de chaîne, sans utiliser la bibliothèque de référence d'exécution , définissez simplement:
#define propertyKeyPath(property) (@""#property)
#define propertyKeyPathLastComponent(property) [[(@""#property) componentsSeparatedByString:@"."] lastObject]
Et alors vous pouvez faire quelque chose comme ça:
NSLog(@"%@", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
Vous pouvez vérifier mon approche sur Gist pour obtenir la chaîne d'une propriété avec auto-complétion et vérification à la compilation.
Récupère le nom de la propriété pour un class :
@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";
Récupère le nom de la propriété pour un protocole :
@protocol AnyProtocol
@property (strong) NSDate *date;
@end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> @"date"
Vous pouvez utiliser
NSString *str = NSStringFromSelector(@selector(crazyObject));
La bonne chose à propos de cette approche est que:
crazyObject
pour vous.crazyObject
à myCrazyObject
, Xcode ajoutera un avertissement disant "unrecognized selector!"
- plutôt bon pour le débogage.J'utilise cette méthode si souvent que j'ai même créé une fonction qui permet d'écrire moins de lettres:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Maintenant, votre solution finale peut ressembler à ceci:
NSString *str = sfs(@selector(crazyObject));
Non conventionnel, hacky, moche, tard, mais ... aussi fort que son nom le permet et fonctionne à merveille:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"."] lastObject] componentsSeparatedByString:@" "] lastObject] stringByReplacingOccurrencesOfString:@"]" withString:@""] : @""
Exemple d'utilisation:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname));
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate]));
...