How do I enumerate on NSString? - objective-c

How do I enumerate on NSString?
example of what I am trying to do:
enum eCat{
dog,
cat,
mouse,
bunny
};
#interface
#implementation
....
enum eCat Cate;
NSString *yoda = #"mouse";
Cate = [yoda intValue];
NSLog(#"Hello: %d",Cate);
wanting the result to be
Hello: 2
thanks

You have to create a mapping (string → enumeration) yourself. One possibility would be something like this (disclaimer: only brain-compiled):
#define CAT_(a, b) a##b
#define CAT(a, b) CAT_(a, b)
#define E(en) [NSNumber numberWithInt:en], CAT(#, #en),
NSDictionary *mapping = [NSDictionary dictionaryWithObjectsAndKeys:
// ...
E(mouse)
E(bunny)
nil];
NSNumber *result = [mapping objectForKey:#"mouse"];
if (!result) {
// ... oops
} else {
enum eCat cate = [result intValue];
}

There's no direct support of such an enumeration in Objective-C.
Instead, create an array of the strings and look for an entry:
static NSArray* enumeration=nil;
if(!enumeration){
enumeration=[[NSArray arrayWithObjects:#"AAA",#"BBB",#"CCC",nil] retain];
}
then use it later:
NSInteger i=[enumeration indexOfObject:#"BBB"];
/* i is now 1 */
It's unrelated to your question, but please don't start a variable name with a capital letter, like your Cate. That's against the convention of Objective-C.

Related

NSExpression to concatenate two KVC strings

Scenario:
I have an NSObject 'A' representing a relationship between two other NSObjects 'B and C' respectively. Both B and C have properties (NSStrings) retrieved using KVC.
I would like to use NSExpression in the body of an 'A' accessor to derive a compound string. So:
[B valueForKey:#"oneString"] returns 'Foo'
[C valueForKey:#"oneString"] returns 'Bar'
// And I would like to dynamically form
[A valueForKey:#"oneString"] returns 'Foo:Bar'
I know the obvious of 'stringWithFormat' but I need to allow for different expressions using different keyPaths determined at run-time.
Could you do:
- (id)valueForUndefinedKey:(NSString*)key
{
return [NSString stringWithFormat:#"%#:%#", [self.a valueForKey:key], [self.b valueForKey:key]];
}
Just to spell the thoughts from comments out in a more complete answer:
You can define oneString as a readonly property:
#property (readonly) NSString* oneString;
Then define a custom accessor:
- (NSString *)oneString {
// ... do your logic here
NSString *firstStr = ...
NSString *secondStr = ...
return [NSString stringWithFormat:#"%#:%#", firstStr, secondStr];
}
+ (NSSet *) keyPathsForValuesAffectingOneString {
return [NSSet setWithObjects: #"self.a", #"self.b", #"self.c", nil];
}
Typed directly into the browser, so beware of typos

how to do string enums and such in objective c

I have filenames and such that are NSStrings. I have to keep typing them in and I may mistype and cause an error. How does the experienced objective C programmer handle this so they only type it in once?
In the header:
extern NSString * const kMyFile;
in the implementation file
NSString * const kMyFile = #"FileName.txt";
You get code completion on it this way.
Or, if it is only being used in one class and doesn't need to be exposed in the header you could just do
static NSString * const kMyFile = #"FileName.txt";
- (void)viewDidLoad {
//assign names here
//create variable names in the header--> NSArray *filename;
filename = [[NSArray alloc] initWithObjects: #"File1", #"File2", nil];
//[filename objectAtIndex: i] --> NSString
}

Get property name as a string

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"
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);
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);
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.
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;
}
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;
}
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;
}
You can use
NSString *str = NSStringFromSelector(#selector(crazyObject));
The good thing about this approach is that:
Xcode will autocomplete word crazyObject for you.
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));
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
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"
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
...

Is there a concise way to map a string to an enum in Objective-C?

I have a string I want to parse and return an equivalent enum. I need to use the enum type elsewhere, and I think I like how I'm defining it. The problem is that I don't know a good way to check the string against the enum values without being redundant about the order of the enums.
Is there no option other than a big if/else?
typedef enum {
ZZColorRed,
ZZColorGreen,
ZZColorBlue,
} ZZColorType;
- (ZZColorType)parseColor:(NSString *)inputString {
// inputString will be #"red", #"green", or #"blue" (trust me)
// how can I turn that into ZZColorRed, etc. without
// redefining their order like this?
NSArray *colors = [NSArray arrayWithObjects:#"red", #"green", #"blue", nil];
return [colors indexOfObject:inputString];
}
In Python, I'd probably do something like the following, although to be honest I'm not in love with that either.
## maps url text -> constant string
RED_CONSTANT = 1
BLUE_CONSTANT = 2
GREEN_CONSTANT = 3
TYPES = {
'red': RED_CONSTANT,
'green': GREEN_CONSTANT,
'blue': BLUE_CONSTANT,
}
def parseColor(inputString):
return TYPES.get(inputString)
ps. I know there are color constants in Cocoa, this is just an example.
try this: Map enum to char array
Pseudo code.. untested.
int lookup(const char* str) {
for(name = one; name < NUMBER_OF_INPUTS; name++) {
if(strcmp(str, stats[name]) == 0) return name;
}
return -1;
}
A more objective-c'ish version of the code could be:
// build dictionary
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
for(i=0; i<number_of_strings; i++) {
[dict setObject:[NSNumber numberWithInteger:i] forKey:[NSString stringWithUTF8String:names[i]]];
}
// elsewhere... lookup in dictionary
id obj = [dict objectForKey:name];
if(obj) return [obj intValue];
return -1;
This has already been answered: Converting between C enum and XML
Basically, you wind up defining corresponding strings when you define your enum, and then you use a category on NSArray so that you can do this:
static NSArray* colorNamesArray = [[NSArray alloc] initWithObjects:colorNames];
//colorNames is a nil-terminated list of string literals #defined near your enum
NSString* colorName = [colorNamesArray stringWithEnum:color];
//stringWithEnum: is defined with a category
Sure, the #define is a little ugly, but the code above, which is what you'll work with most of the time, is actually pretty clean.
I was never satisfied with any of the suggestions. (But I appreciate the effort that went into them.) I tried a few of them but they didn't feel good or were error-prone in practice.
I ended up created a custom dictionary to map integers to strings which feels a lot better because it's Cocoa through and through. (I didn't subclass NSDictionary in order to make it harder to misuse.)
#interface ZZEnumDictionary : NSObject {
NSMutableDictionary *dictionary;
}
+ (id)dictionary;
+ (id)dictionaryWithStrings:(id)firstString, ...;
- (NSString *)stringForInt:(NSInteger)intEnum;
- (NSInteger)intForString:(NSString *)stringEnum;
- (BOOL)isValidInt:(NSInteger)intEnum;
- (BOOL)isValidString:(NSString *)stringEnum;
- (BOOL)stringEquals:(NSString *)stringEnum intEnum:(NSInteger)intEnum;
- (BOOL)setContainsString:(NSSet *)set forInt:(NSInteger)intEnum;
- (NSArray *)allStrings;
#end
#interface ZZEnumDictionary ()
- (void)setInt:(NSInteger)integer forString:(NSString *)string;
#end

Anyway to get string from variable name?

Say I have my class
#interface Person : NSObject { NSString *name; }
I need to get the name of NSString's within my class
Person *person = [[Person alloc] init];
NSLog(#"Name of variable %s\n", _NameofVariable_(person->name));
Thanks for the answers, here's the solution I came up from the replies
//returns nil if property is not found
-(NSString *)propertyName:(id)property {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self, thisIvar) == property)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
As easy as
#define VariableName(arg) (#""#arg)
Then you do:
NSObject *obj;
NSString *str = VariableName(obj);
NSLog(#"STR %#", str);//obj
You can get the names of a class's instance variables with the Objective-C runtime API function class_copyIvarList. However, this is rather involved, rarely done and almost never the best way to accomplish something. If you have a more specific goal in mind than mere curiosity, it might be a good idea to ask about how to accomplish it in Objective-C.
Also, incidentally, person.name doesn't specify an instance variable in Objective-C — it's a property call. The instance variable would be person->name.
You might use preprocessor stringification and a bit of string twiddling:
NSUInteger lastIndexAfter(NSUInteger start, NSString *sub, NSString *str) {
NSRange found = [str rangeOfString:sub options:NSBackwardsSearch];
if(found.location != NSNotFound) {
NSUInteger newStart = NSMaxRange(found);
if(newStart > start)
return newStart;
}
return start;
}
NSString *lastMember(NSString *fullName) {
if(!fullName) return nil;
NSUInteger start = 0;
start = lastIndexAfter(start, #".", fullName);
start = lastIndexAfter(start, #"->", fullName);
return [fullName substringFromIndex: start];
}
#define NSStringify(v) (##v)
#define _NameofVariable_(v) lastMember(NSStringify(v))
If the person object is exposed as a property of the class, you can use objc_msgSend to get the value.
So, if you could access person using
[object person]
You could also do
objc_msgSend(object, "person")
For more details on message sending, including how to pass arguments to methods, see the Objective-C Runtime Programming Guide section on Messaging
The following works as a macro:
#define STRINGIZE(x) #x