25

I need a way to pass a property and get the name assigned to it. Any suggestions?

@property (nonatomic, retain) MyObject *crazyObject;

NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return @"crazyObject"
jscs
  • 63,694
  • 13
  • 151
  • 195
aryaxt
  • 76,198
  • 92
  • 293
  • 442
  • 1
    In the code `SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject)`, `crazyObject` is not a property — it's an instance variable that's an argument to a function. Inside the function it's just a parameter that has the same value as the instance variable. Actually getting at the property itself in order to pass it to a function would involve more code. As it stands, what you're asking for is basically for `SOME_WAY_TO_GET_PROPERTY_NAME()` to be translated to `@""`. This is actually doable – `#define SOME_WAY_TO_GET_PROPERTY_NAME(n) @#n` — but not very useful. Can you explain the use case for this? – Chuck Jul 07 '11 at 20:02
  • I define my instance variables with an _ for example @synthesize crazyObject = _crazyObject; – aryaxt Jul 07 '11 at 22:45
  • 1
    Then `crazyObject` means nothing in that context. It still doesn't identify a property. So again: Can you explain the use case for this? What you're trying to do is unclear, because if you have the property's name to pass to this function, you can just surround it in quotes. – Chuck Jul 08 '11 at 00:47
  • Check this out http://www.g8production.com/post/78429904103/get-property-name-as-string-without-using-the-runtime – onmyway133 Nov 15 '14 at 15:24

10 Answers10

24

You can try this:

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);
Oscar Gomez
  • 18,436
  • 13
  • 85
  • 118
  • 2
    That'll print all property names. There's no reflective way for a class to say "which property do I return this value for?" other than querying all the properties and comparing values. – Tommy Jul 07 '11 at 19:26
  • @Tommy yes you would have to compare the values, I don't know if it can be done exactly like @aryaxt wants. – Oscar Gomez Jul 07 '11 at 19:36
  • `[name isEqualToString:@"crazyObject"]` Not terribly useful alone, but opens up the possibility to something like `[[name substringToIndex:[@"crazy" length]] isEqualToString:@"crazy"]` – Slipp D. Thompson Sep 28 '11 at 20:39
20

It's as simple as this...expanding upon what Chuck already mentioned:

#ifndef STR_PROP
    #define STR_PROP( prop ) NSStringFromSelector(@selector(prop))
#endif

You then use it like so:

NSString *strProp = STR_PROP(myProperty);
Arvin
  • 2,516
  • 27
  • 30
  • This works however, what if the property was removed from the class? The ideal solution would to have a check before the runtime and not during. – David Apr 03 '13 at 20:51
  • @Arvin Great solution to avoid stringly typed code. – Steve Moser Sep 25 '15 at 20:08
  • There are some issues with this method: http://corporationunknown.com/blog/2014/03/24/to-nsstringfromselector-or-not/ – iosdude Jul 09 '16 at 06:19
16

Background

Keep in mind that properties are really just, to quote Apple, "a syntactical shorthand for declaring a class’s accessor methods." In fact, by itself, the @property declaration doesn't even work. Your @synthesize statement translates the @property into the equivalent of two methods:

- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;

Which one is used depends on the context surrounding your self.crazyObject. (@synthesize also creates a matching instance variable if you didn't do it yourself.) The offshoot of all this is that you can't really translate to and from a property with one single method.

Proposed Solution

You can use what Apple already provides:

NSString *foo = NSStringFromSelector(@selector(myClassProperty));

Or do something custom:

Given that self.crazyObject really translates to either [self crazyObject] or [self setCrazyObject:foo] by the time your code is running, ou'll probably need two methods, like:

- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;

You might then want at least 2 companion methods such as:

- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;

Within these methods, you can use the Foundation functions NSStringFromSelector and NSSelectorFromString to convert back and forth between SEL and NSString. Use whatever string manipulations you like to convert back and forth between your setter string (setCrazyObject) and your property name (crazyObject).

A complete solution is hard to provide without knowing the exact use case, but hopefully this provides some more clues for anyone trying to accomplish something similar. There might even be some useful things made possible by combining this approach with Oscar's answer.

Kyle Robson
  • 3,060
  • 24
  • 20
clozach
  • 5,118
  • 5
  • 41
  • 54
  • Not really. A formal declared property is an entity in itself from the Objective-C runtime perspective. It has attributes of its own (e.g. ownership), as opposed to an arbitrary pair of getter -X and setter -setX:. –  Oct 20 '11 at 04:26
  • I'll take your word for it as my answer is based on a hazy memory from who-knows-when. I do wish that Apple offered the equivalent of `NSSelectorFromString` and `NSStringFromSelector` for properties. – clozach Oct 26 '11 at 20:56
  • 5
    @clozach - Apple does do that. `NSString *foo = NSSelectorFromString(@selector(myClassProperty));` – Chuck Wolber May 11 '12 at 18:59
  • This will help avoiding a lot of typo – user4951 Nov 04 '12 at 00:46
  • 2
    there's a mistake in your code: ``NSString *foo = NSSelectorFromString(@selector(myClassProperty));`` should be ``NSString *foo = NSStringFromSelector(@selector(myClassProperty));`` – user3099609 Mar 26 '15 at 11:27
4

Here is a function that returns the name of an ivar, so basically it not only returns the properties but any ivar of the class. I haven't found a way to get the property directly so I used the ivar trick.

#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;
}
JustSid
  • 25,168
  • 7
  • 79
  • 97
  • This breaks if more than one ivar has the same value, or even if you have an object ivar set to nil and an integer ivar set to 0. – Chuck Jul 07 '11 at 21:28
  • 1
    @Chuck: Thats true, but this will be the case for any method one could come up with to solve the problem. – JustSid Jul 08 '11 at 06:36
3

After searching and debugging i find solution for me...

Added #import <objc/runtime.h>

Methods object_getIvar(id obj, Ivar ivar) send bad access and app crashes. i modify some code and it worked great:

+(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;
}
biloshkurskyi.ss
  • 1,358
  • 3
  • 15
  • 34
2

Modifying the solution, it works when your object is allocated already, otherwise it returns 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;
}
Mohd Iftekhar Qurashi
  • 2,385
  • 3
  • 32
  • 44
  • 1
    I was receiving a EXEC_BAD_ACCESS exception sometimes when using this method, but only sometimes. When moved to a Obj-C style method I stopped getting the exception. – Michael Ozeryansky Jul 03 '15 at 04:40
2

You can use

NSString *str = NSStringFromSelector(@selector(crazyObject));

The good thing about this approach is that:

  1. Xcode will autocomplete word crazyObject for you.
  2. When later on you will change the property name from crazyObject to myCrazyObject, Xcode will add a warning saying "unrecognized selector!" -- pretty good for debugging.

I use this method so often, that I even created a function, which allows to write less letters:

NSString * __nonnull sfs(SEL __nonnull theSelector)
{
    if (!theSelector)
    {
        abort();
    }
    return NSStringFromSelector(theSelector);
}

Now your final solution can look like this:

NSString *str = sfs(@selector(crazyObject));
OlDor
  • 1,460
  • 12
  • 18
1

From Get property name as string, without using the runtime reference library, just define:

#define propertyKeyPath(property) (@""#property)
#define propertyKeyPathLastComponent(property) [[(@""#property) componentsSeparatedByString:@"."] lastObject]

And then you can do something like this:

 NSLog(@"%@", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
samthui7
  • 923
  • 2
  • 12
  • 11
  • 1
    I don't get it how is this helpful? you are manually inputing the property name, and it's returning exactly what your input is. It's like going to a store paying a $5 bill and getting a $5 bill back. – aryaxt Jun 17 '15 at 17:37
  • Nice solution. Thanks! – Borut Tomazin Jul 07 '15 at 10:13
1

You may check my approach at Gist to get the string for a property with autocompletion and compile-time check.

How to use:

Get the property name for a 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";

Get the property name for a protocol:

@protocol AnyProtocol
@property (strong) NSDate *date;
@end

// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date);    // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date);  // ==> @"date"
Xaree Lee
  • 3,188
  • 3
  • 34
  • 55
1

Unconventional, hacky, ugly, late, but... as strong-named as it gets and works like a charm:

#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"."] lastObject] componentsSeparatedByString:@" "] lastObject] stringByReplacingOccurrencesOfString:@"]" withString:@""] : @""

Sample usage:

NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname)); // surname
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate])); // birthDate
...
manta
  • 369
  • 1
  • 9