I just start to learn ObjectiveC and I have a problem.
I have a variable in a for loop , this variable change value at any iteration and I want get his value.
Here my problem the variable is "name":
for 1st iteration name='id'
for 2nd iteration name='city' ...
for (i = 0; i < count; i++) {
name = ivar_getName(ivars[i]);
encode = ivar_getTypeEncoding(ivars[i]);
type = decode_type_string(encode);
if (strncmp(encode, "#", 1) == 0)
{
printf(" '%s', ", [[obj name*]UTF8String]);
}
else
{
printf(" %s ", name);
}
}
My goal is that the variable is interpreted by its value. if in my iiteration variable name is set to "ID" I want to have: [obj name] => [id obj] (for the id of my instance) if in my iiteration variable name is set to "City" I want to have: [obj name] => [obj City] (for the city of my instance)
(sorry for my english)
I think you are badly confused about several things. First off, NSString objects do not begin with "#" as the first character of the string. Rather, the "#" character is used to differentiate between a C string literal (eg, "abc") and an Objective-C NSString literal (eg, #"abc"). Note that the "#" character is outside the quotes.
Second, if you have an object with a PROPERTY named "name" you may reference its value with either [myObj name] or myObj.name, and set it either with [myObj setName:newValue]; or with myObj.name = newValue;.
If you have an NSString object you wish to print for diagnostic purposes, you can print it using NSLog(#"Here is the string ->%#", theString);. The %# is a special Objective-C formatting code meaning "treat the next format parm as an object and invoke it's description method, then display that result".
(And if you're "just learning" Objective-C there's no reason to be mucking with ivar_getName and its ilk. Functions like that are very rarely needed.)
If you are printing an Objective-C string, use NSLog
NSLog(#" %# ", [obj name]);
To print a string in objective-c you can use NSLog. The call looks like
NSLog(#"%#",[obj name]);
Not sure I quite read your question right - but I think you want to dynamically fetch them.
See below for an example.
Dw.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface Frob : NSObject;
#property (strong) NSString * name, * bar, * fred;
#end
#implementation Frob
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Frob * frob = [[Frob alloc] init];
frob.name = #"aName value";
frob.bar = #"aBar value";
frob.fred = #"aFred value";
const char *lst[] = { "name", "bar", "fred" };
for(int i = 0; i < sizeof(lst)/sizeof(char*); i++)
{
const char * nameStr = lst[i];
SEL s = sel_getUid(nameStr);
if (s) {
NSString * val = [frob performSelector:s];
NSLog(#"%s --> %#\n", nameStr, val);
printf("%s --> %s", nameStr, [val UTF8String]);
}
}
}
return 0;
}
Related
I'm quite new to Cocoa programming and I'm tiring my best to create a program which will have the user input text into a text field and then press a button. When the button is pressed the text is supposed to replace certain substrings to certain characters. None of the substrings are longer than 2 characters, though some are a single character long. After the replacement has been performed the newly acquired text is to be put into another textfield.
Examples of substring replacements may be that "n" is supposed to be changed to "5", "nj" is supposed to be changed to "g" and "ng" is to be changed to "s". So the text "Inject the syringe now!" would be changed to "Igect the syrise 5ow!"
How can I achieve this in a simple and elegant way? I have tried the following code but it doesn't seem to work.
- (IBAction)convert:(id)sender {
NSMutableString *x;
[x setString:[self.input stringValue]];
NSMutableString *output1;
[output1 setString:#""];
NSMutableString *middle;
middle = [[NSMutableString alloc] init];
int s;
unsigned long length = [x length];
for (s = 0; s < length; s = s + 1) {
if (s + 2 <= length) { // if more than or equal to two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 2)]];
if ([middle isEqualToString:#"nj"]) {
[output1 appendToString:#"g"];
s = s+1;
} else if ([middle isEqualToString:#"ng"]) {
[output1 appendToString:#"s"];
s = s+1;
} else { // if no two-character sequence matched
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
} else { // if less than two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
}
[self.output setStringValue:output1];
}
Here, *x is where the text from input goes, *output1 is where the result is stored, *middle consists of the piece of text being tested, and input and output are the NSTextFields.
I guess you could achieve what you want with a quite a few different methods. Here is a simple one:
Define a map for values/replacements
Sort them by length (largest length first)
Match and replace
Something like this perhaps:
#import <Foundation/Foundation.h>
NSString * matchAndReplace(NSString *input, NSDictionary *map){
NSMutableString *_input = [input mutableCopy];
// Get all keys sorted by greatest length
NSArray *keys = [map.allKeys sortedArrayUsingComparator: ^(NSString *key1, NSString *key2) {
return [#(key2.length) compare:#(key1.length)];
}];
for (NSString *key in keys) {
[_input replaceOccurrencesOfString:key
withString:map[key]
options:NSLiteralSearch
range:NSMakeRange(0,_input.length)];
}
return [_input copy];
};
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *mapping = #{
#"n": #"5",
#"nj": #"g",
#"ng": #"s"
};
NSString *input = #"Inject the syringe now!";
NSLog(#"Output: %#", matchAndReplace(input, mapping));
}
}
Which will produce:
Output: Igect the syrise 5ow!
Note: This is an over-simplified way to achieve what you want (obviously) and maybe requires a few adjustments to cover every edge case, but it's simpler than your version and I hope that will be helpful to you.
I, like many, constantly have to look up date codes for date formatter. I decided to make a file that will make it easier for me to remember them all. I include a function for readability that I declare like this:
NSString * dateFormatString(NSString * string1, ...) {
// Parse out Args
va_list args;
va_start(args,string1);
// Set up our Format String
NSMutableString * formatString = [NSMutableString string];
// Build Format string
for (NSString * arg = string1; arg != nil; arg = va_arg(args, NSString*)) {
[formatString appendString:arg];
}
va_end(args);
return formatString;
}
So, I can then program my NSDateFormatter like this:
dateFormatter.dateFormatString = dateFormatString(DKDayOfWeekFull, #", ", DKMonthNameFull, #" ", DKDayOfMonthComplete, nil);
You could do achieve pretty much the same thing by declaring:
[NSString stringWithFormat:#"%#, %# %#", DKDayOfWeekFull, DKMonthNameFull, DKDayOfMonthComplete];
However, if you're describing a date with more variables like "Sat, Jan 14 2006 at 7:52 AM" it would have to be:
NSString * dateFormatterString = [NSString stringWithFormat:#"%#, %# %# %# 'at' %#:%# %#", DKDayOfWeekAbbreviated, DKMonthNameAbbreviated, DKDayOfMonthComplete, DKYearComplete, DKHour12hrComplete, DKMinutes2Digits, DKAmPm];
Which I personally think is a bit more readable like this:
NSString * dateFormatterString = dateFormatString(DKDayOfWeekAbbreviated, #", ", DKMonthNameAbbreviated, #" ", DKDayOfMonthComplete, #" ", DKYearComplete, #"'at' ", DKHour12hrComplete, #":", DKMinutes2Digits, #" ", DKAmPm, nil);
Question
I would prefer a way to iterate through the variables without having to pass nil into the function. Is there another way to iterate through a variable argument list, other than:
for (NSString * arg = string1; arg != nil; arg = va_arg(args, NSString*)) {
[formatString appendString:arg];
}
I assume you meant to say dateFormatter.dateFormat = ..., since NSDateFormatter has no dateFormatString property.
I assume DKDayOfWeekAbbreviated is a string constant defined as #"E", and DKDayOfWeekFull is #"EEEE", and so on, based on UTS #35.
If that is so, here's a different approach. Define your constants like this:
#define DKDayOfWeekAbbreviated #"E"
#define DKDayOfWeekFull #"EEEE"
#define DKMonthNameFull #"MMMM"
#define DKDayOfMonthComplete #"dd"
Then use compile-time string concatenation to build your strings. The compiler merges two adjacent string constants. For example, "hello " "world" becomes "hello world", and #"hello " #"world" becomes #"hello world". In fact, you can omit the second and later # characters, so #"hello " "world" becomes #"hello world".
Thus:
dateFormatter.dateFormat = DKDayOfWeekFull ", " DKMonthNameFull " " DKDayOfMonthComplete;
You don't need a helper function or varargs.
The only way to support variable arguments using the standard C syntax is to do what you are doing.
But you have another option - use an NSArray.
Your function becomes:
NSString * dateFormatString(NSArray *strings) {
// Set up our Format String
NSMutableString * formatString = [NSMutableString string];
// Build Format string
for (NSString * arg in strings) {
[formatString appendString:arg];
}
return formatString;
}
or simply do:
NSString * dateFormatString(NSArray *strings) {
return [strings componentsJoinedByString:#""];
}
And you call it like this:
dateFormatter.dateFormatString = dateFormatString(#[ DKDayOfWeekFull, #", ", DKMonthNameFull, #" ", DKDayOfMonthComplete ]);
No need for nil using the modern NSArray syntax.
Since property named "age" would always have a selector named "age" as well, I could use respondsToSelector as this question suggests and that will tell me if a particular selector exists at runtime in any given object.
If a property named "age" exists, I can verify that. How could I know if that selector (the read method for that property) returns an object (id) or non-object (int)?
Is such type determination possible at runtime, or is the Objective-C way to always assume that someone implemented that method using the type I'm hoping it used, or can I also verify the return type?
This is using the latest Objective-C version (LLVM 4.1) in XCode 4.5.
Update: This is the utility-category-on-NSObject that I came up with:
- (NSString*) propertyType: (NSString*)propname
{
objc_property_t aproperty = class_getProperty([self class], [propname cStringUsingEncoding:NSASCIIStringEncoding] ); // how to get a specific one by name.
if (aproperty)
{
char * property_type_attribute = property_copyAttributeValue(aproperty, "T");
NSString *result = [NSString stringWithUTF8String:property_type_attribute];
free(property_type_attribute);
return result;
}
else
return nil;
}
While looking into this question I also wrote this handy-dandy utility method that
can list all the properties on this object:
- (NSArray*) properties;
{
NSMutableArray *results = [NSMutableArray array];
#autoreleasepool {
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char * aname=property_getName(property);
[results addObject:[NSString stringWithUTF8String:aname]];
//const char * attr= property_getAttributes(property);
//[results addObject:[NSString stringWithUTF8String:attr]];
}
if (properties) {
free(properties);
}
} // end of autorelease pool.
return results;
}
You could use class_copyPropertyList to get a list of properties declared in a class.
class_copyPropertyList
Describes the properties declared by a class.
And then property_getAttributes:
property_getAttributes
Returns the attribute string of an property.
Here you can find some more concrete hints and examples.
As a side note, the following statement:
Since property named "age" would always have a selector named "age" as well
is not correct, since a property can have custom getter and/or setter:
#property (nonatomic, getter=isImmediate) BOOL immediate;
EDIT:
Some sample code I found in another S.O. post:
const char * type = property_getAttributes(class_getProperty([self class], "myPropertyName"));
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString components separatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else ....
One approach you can take, assuming you know the property name already, is to use the class_getProperty function. You can also use the property_copyAttributeValue() function to get just a particular attribute by name:
objc_property_t number_property = class_getProperty([MyClass class], "number");
char *number_property_type_attribute = property_copyAttributeValue(number_property, "T");
NSLog(#"number property type attribute = %s", number_property_type_attribute);
Will log:
2013-01-14 14:45:37.382 RuntimeFun[61304:c07] number property type
attribute = i
Assuming MyClass looks something like:
#interface MyClass : NSObject
#property (nonatomic) int number;
#end
#implementation MyClass
#end
One you have your type attribute string, you can then compare it to the various Objective-C type encodings. Once you're done with your comparison, be sure to call free() on your attribute 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
...
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