Objective-C accessing float getters with variable names - objective-c

Let's say I have an NSArray called myArray of NSStrings (#"a0",#"a1",#"a2")
Then in a fast enumeration I loop into my array to build properties according to that NSStrings. I've got a problem accessing that properties.
I'm trying something like that :
#property (nonatomic) float a0propertyLow;
#property (nonatomic) float a0propertyHigh;
#property (nonatomic) float a1propertyLow;
#property (nonatomic) float a1propertyHigh;
..
.. etc.
for (NSString *aPos in myArray) {
NSString *low = [NSString stringWithFormat:#"%#propertyLow",aPos];
NSString *high = [NSString stringWithFormat:#"%#propertyHigh",aPos];
SEL lowSel = NSSelectorFromString(low);
SEL highSel = NSSelectorFromString(high);
if ([self respondsToSelector:lowSel]&&[self respondsToSelector:highSel]) {
id sumPartOne = [self performSelector:lowSel];
id sumPartTwo = [self performSelector:highSel];
float bla = (float)sumPartOne + (float)sumPartTwo;
}
}
I know my code is wrong but I don't know how to make it work.
My problem is that lowSel and highSel are getters which returns float but the perform selector method returns id which is ok for an object but not for floats.
So, how can I access my float getters with variable names ? I'm sure answer must be simple but it seems that my mind is looking for something complicated (and which obviously doesn't work) so I'm asking for help :)
Thank you very much for your help

You can't use performSelector: to call a method that returns a scalar value. The documentation for performSelector: clearly says what you have to do:
For methods that return anything other than an object, use NSInvocation.
An NSInvocation is a little more complex to set up but more flexible regarding arguments and return types.
In your case, it is probably easier to use Key-Value Coding instead:
[self valueForKey:low];
takes the return type into account and will automatically wrap the float in an NSNumber.

If you really need to use these getter methods, you can change your properties to double and use objc_msgSend_fpret():
#include <objc/runtime.h>
#include <objc/message.h>
double arg0 = objc_msgSend_fpret(self, lowSel);
If you can avoid getters (I know, that's not good practice, but anyway, it works for sure with the following method), and use the instance variables directly:
void *object_getIvarPtr(id obj, const char *name)
{
if (!obj || !name)
{
return NULL;
}
Ivar ivar = object_getInstanceVariable(obj, name, NULL);
if (!ivar)
{
return NULL;
}
return ((char *)obj + ivar_getOffset(ivar));
}
float arg0 = *(float *)object_getIvarPtr(self, [lowSel UTF8String]);
Hope this helps.

One way you can do is convert your floats into objects at runtime such as:-
NSString *str=[NSSTring stringWithFormat:#"%f",yourFloatValue];
and then u can retrive it using
[str floatValue];

Related

NSNumber numberWithBool not an objective-c object

I'm need to store some bool values in NSMutableDictionary, so I've founded a solution: using a [NSNumber numberWithBool] to make an object from bool.
It's work fine with value YES, but with NO it sometimes work fine, and sometimes result is "Not an objective-c object".
MyCode:
$ //sometimes fine, but sometimes "not an objective-c object"
$ NSNumber* nsn = [NSNumber numberWithBool:NO];
I'm new to Objective-C, so i'm sorry if this is basic problem, but i really stuck with it.
Thanks for help.
The object returned by numberWithBool and initWithBool seems to be initializing the NSNumber by a string and when you map the object in a dictionary it takes NSValue which indeed is null. Hence the dictionary will say it as null.
Try either of:
NSObject * y1 = #(YES) ;
NSObject * n1 = #(NO) ;
NSObject * y2 = #YES ;
NSObject * n2 = #NO ;
Another example:
NSDictionary * testDict = #{
#"key1": #YES
, #"key2": #NO
} ;
BOOL b = [testDict[#"key1"] boolValue] ;
Dude. Why don't you just create your own class:
#interface Fool
{
bool _amIFool;
}
#property bool amIFool;
#end
#implementation
-(bool)amIFool
{
return _amIFool;
}
-(void)setAmIFool:(bool)amIFool
{
_amIFool = amIFool
}
#end
Then you can shove it in a dictionary. Above code is just an example. You can write something better....
EDIT: You can even write a comparator, so you could directly do comparisons and stuff. And even write a convenience method so it is easy to initialise...
EDIT: Just FYI, my initial implementation was called "Cool". But since I'm not cool, I chose "Fool". You can do either.

How to loop back the attribute in an NSObject?

For example the Object is something like this:
MyUser: NSObject{
NSString *firstName;
NSString *lastName;
NSString *gender;
int age;
}
and I would like to compare to user, if their attributes are the same, I will treat it as equal... instead of write a static method to compare enough attribute one by one, can I have a lazy way to get all the attribute to compare themselves, Thanks.?
For comparison, this is what you're trying to avoid writing.
-(NSUInteger)hash {
return [firstName hash] ^ [lastName hash] ^ [gender hash] ^ age;
}
-(BOOL)isEqual:(id)other {
return [other isKindOfClass:[self class]]
&& age == other.age
&& [gender isEqualToString:other.gender]
&& [firstName isEqualToString:other.firstName]
&& [lastName isEqualToString:other.lastName];
}
Using XOR is an extremely simple way of combining hashes, and I mostly include it as a stand-in. It may hurt the quality of the hash value, depending on distribution of the underlying hash functions. If the hashes have a uniform distribution, it should be all right. Note also that combining hashes only works because NSStrings that are equal in content have the same hashes. This approach won't work with all types; in particular, it won't work with types that use the default implementation of hash.
To get around writing the above, first change the type of the age property to NSNumber, so it doesn't have to be handled as a special case. You don't have to change the ivar, though you can if you want.
#interface MyUser : NSObject {
...
unsigned int age; // Or just make this an NSNumber*
}
...
#property (assign,nonatomic) NSNumber *age;
#implementation MyUser
#synthesize firstName, lastName, gender;
/* if the age ivar is an NSNumber*, the age property can be synthesized
instead of explicitly defining accessors.
*/
#dynamic age;
-(NSNumber*)age {
return [NSNumber numberWithUnsignedInt:age];
}
-(void)setAge:(NSNumber*)newAge {
age = [newAge unsignedIntValue];
}
Second, make sure your class supports the fast enumeration protocol. If it doesn't, you can implement -countByEnumeratingWithState:objects:count: by making use of reflection (with the Objective-C runtime functions) to get the list of properties for instances of your class. For example (taken in part from "Implementing countByEnumeratingWithState:objects:count:" on Cocoa With Love):
#import <objc/runtime.h>
...
#interface MyUser (NSFastEnumeration) <NSFastEnumeration>
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
#end
#implementation MyUser
#synthesize firstName, lastName, gender;
/* defined in the main implementation rather than a category, since there
can be only one +[MyUser initialize].
*/
static NSString **propertyNames=0;
static unsigned int cProperties=0;
+(void)initialize {
unsigned int i;
const char *propertyName;
objc_property_t *properties = class_copyPropertyList([self class], &cProperties);
if ((propertyNames = malloc(cProperties * sizeof(*propertyNames)))) {
for (i=0; i < cProperties; ++i) {
propertyName = property_getName(properties[i]);
propertyNames[i] = [[NSString alloc]
initWithCString:propertyName
encoding:NSASCIIStringEncoding];
}
} else {
cProperties = 0;
// Can't initialize property names. Fast enumeration won't work. What do?
}
}
...
#end
#implementation MyUser (NSFastEnumeration)
-(NSUInteger)
countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id *)stackbuf
count:(NSUInteger)len
{
if (state->state >= cProperties) {
return 0;
}
state->itemsPtr = propertyNames;
state->state = cProperties;
state->mutationsPtr = (unsigned long *)self;
return cProperties;
}
#end
Last, implement hash (using fast enumeration) and isEqual:. Hash should calculate the hashes of all properties, then combine them to create the hash for the MyUser instance. isEqual: can simply check the other object is an instance of MyUser (or a subclass thereof) and compare hashes. For example:
-(NSUInteger)hash {
NSUInteger myHash=0;
for (NSString *property in self) {
// Note: extremely simple way of combining hashes. Will likely lead
// to bugs
myHash ^= [[self valueForKey:property] hash];
}
return myHash;
}
-(BOOL)isEqual:(id)other {
return [other isKindOfClass:[self class]]
&& [self hash] == [other hash];
}
Now, ask yourself which is less work overall. If you want a single approach what will work for all your classes, it might be the second (with some changes, such as turning +initialize into a class method on NSObject that returns the property name array and length), but in all likelihood the former is the winner.
There's a danger in both of the above hash implementations with calculating the hash based on property values. From Apple's documentation on hash:
If a mutable object is added to a collection that uses hash values to determine the object’s position in the collection, the value returned by the hash method of the object must not change while the object is in the collection. Therefore, either the hash method must not rely on any of the object’s internal state information or you must make sure the object’s internal state information does not change while the object is in the collection.
Since you want isEqual: to be true whenever two objects have the same property values, the hashing scheme must depend directly or indirectly on the object's state, so there's no getting around this danger.

When putting an Outlet in Xcode into a variable, don't know what kind of pointer to use?

So I was going through Cocoa Programming for Mac OS X for Dummies by Erick Tejkowski. After doing the calculator example, I got the basics of Objective-C in Xcode, since I know basic stuff. I got that to put what's in a text field into a variable, you first have to put the text field as an outlet in the header file like this:
#import <Cocoa/Cocoa.h>
#interface Mah_Application__It_is_awesomeAppDelegate : NSObject <NSApplicationDelegate>
{
NSWindow *window;
IBOutlet id hi;
}
- (IBAction)Calculate:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#end
And then you put it in a variable like so:
- (IBAction)Calculate:(id)sender
{
int something;
hi = [hi intValue];
}
However, when I want a Boolean value, like a checkbox or something, or perhaps even a radio group, I don't know what pointer to use in place of intValue. For a moment, assume the variable hi is now a boolean. I tried this:
- (IBAction)Calcluate:(id)sender
{
BOOL something;
something = [hi BOOL];
}
but it says that's not a valid pointer. What should I use, then?
boolValue is defined for NSString and NSNumber, so it depends on what type hi is
- (IBAction)Calcluate:(id)sender
{
BOOL something = [hi boolValue];
}
Edit (I re-read the question and you might want what the other answer from Stew suggested):
BOOL myBool = [#"1" boolValue];
myBool = [someObject boolValue];
These are some of the conversion methods for NSString - Also see the NSString Reference Here:
- (double)doubleValue;
- (float)floatValue;
- (int)intValue;
- (NSInteger)integerValue;
- (long long)longLongValue;
// (boolValue) Skips initial space characters (whitespaceSet),
// or optional -/+ sign followed by zeroes.
// Returns YES on encountering one of "Y", "y", "T", "t", or a digit 1-9.
// It ignores any trailing characters.
- (BOOL)boolValue;
Also, from NSNumber Class Reference:
boolValue Returns the receiver’s value as a BOOL.
- (BOOL)boolValue
Return Value The receiver’s value as a BOOL,
converting it as necessary.
Special Considerations Prior to Mac OS X v10.3, the value returned
isn’t guaranteed to be one of YES or NO. A 0 value always means NO or
false, but any nonzero value should be interpreted as YES or true.
Try this:
[NSNumber numberWithBool:YES]
[NSNumber numberWithBool:NO]
[NSNumber numberWithInt:1]
[NSNumber numberWithFloat:0.25]
// etc...
- (IBAction)Calcluate:(id)sender
{
BOOL something;
something = [hi [NSNumber numberWithBool:BOOL]];
}
NSNumber reference
Since things like BOOL, NSInteger, NSUInteger, etc are not pointers, they cannot do certain things like be values for a dictionary, etc. For this purpose, NSNumber was created. It wraps these types in a class so they can be used as arguments for passing to selectors, or stored in NSDictionary's.
Here is a common thing I do:
[self.myLoadingView performSelectorOnMainThread:#selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];

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
...

why does my objective c method give me a "error: can not use an object as parameter to a method"

I've looked at this over and over again and I can't see the problem. Its probably obvious and I'm probably being an idiot and I apologize in advance for this.
In my interface I have:
#interface PolygonShape : NSObject
{
int numberOfSides;
int minimumNumberOfSides;
int maximumNumberOfSides;
}
#property int numberOfSides, minimumNumberOfSides, maximumNumberOfSides;
// class methods
+ (float)getAngleInDegrees:(PolygonShape *) polyshape;
+ (float)getAngleInRadians:(PolygonShape *) polyshape;
+ (NSString)getName:(PolygonShape *) polyshape;
//instance methods
- (id)init;
- (id)initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min
maximumNumberOfSides:(int)max;
#end
The part in the implementation that I get errors is for the getName method:
#implentation...
+ (NSString)getName:(PolygonShape *) polyshape
{
// here is where I get the "error: can not use an object as parameter to a method"
int sides = [polyshape numberOfSides];
NSString * s = [NSString init];
switch (sides) {
case 3:
s = "#Triangle";
// there's also an "assignment from incompatible pointer type" warning...but its secondary
break;
case 4:
return "#Square";
break;
default:
break;
}
}
The thing that drives me batty is that the class methods works just fine:
+ (float)getAngleInDegrees:(PolygonShape *) polyshape;
+ (float)getAngleInRadians:(PolygonShape *) polyshape;
Your getName: method should return (NSString *), not (NSString). I assume this is the error; if so, then yes, the error message could definitely have been more informative.
In fact, in Objective-C you will never see objects getting passed around without their * behind them, not as return values, not as parameters, not as local variables and not as member variables.
BTW, the warning you mention is because you have a typo, mixing up "#foo" with #"foo". The latter is an Objectice-C string literal, the former is a C string literal whose first character just happens to be #.
In addition to the other answers, you're using [NSString init] where you should be using [[NSString alloc] init]. However, that will only allocate an empty string, so you'd probably be better off initializing s to either #"" or nil.
I think the error is slightly misleading in this case. In Objective-C, it's generally not possible to pass an object by value (including return values). In this case, you declare the return value as NSString rather than NSString*. The declaration should be:
+ (NSString*)getName:(PolygonShape *) polyshape
not
+ (NSString)getName:(PolygonShape *) polyshape