Assigning a value to id doesn't stick - objective-c

I'm reading a plist from a file and want to set a number of properties to the values from the file, but only if they're not nil, so I came up with a little method, but it doesn't work as expected:
- (void)assignNonNullValue:(id)value to:(id)target
{
if (value) {
target = value;
NSLog(#"new value = %#", target);
}
}
// ...
NSString *foo = #"a";
[self assignNonNullValue:#"b" to:foo];
NSLog(#"foo=%#", foo);
I was expecting the following output:
new value = b
foo=b
but instead I got:
new value = b
foo=a
Why?

Here's another approach:
- (id)valueOrOriginalIfNull:(id)value original:(id)original {
if (value) {
return value;
} else {
return original;
}
}
// ...
NSString *foo = #"a";
foo = [self valueOrOriginalIfNull:#"b" original:foo];
NSLog(#"foo=%#", foo);
The reason your original approach fails is that parameters are passed by value. When you assign target to another value inside the method, it has no affect on the original value.

You're changing target value in your method. It won't affect value of foo in another scope. It want to change foo value, you need to pass your method where it points to. So that, when you change in a method, it'll affect foo value as you're changing the value it points to. To clarify my explanation, here is the code
- (void)assignNonNullValue:(id)value to:(id *)target
{
if(value != nil)
*target = value;
}
It's really really rare that you use id * as id is a already pointer to an object. .Passing id * as a parameter allows you to change the value where it points to. So that, it's going to be permanent.
After you've made the changes i pointed. You need to call this method as
[self assignNonNullValue:#"b" to:&foo];

Related

set ivars from NSDictionnary

I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];

How to determine if an object is in an array based on property value / how to use selectors?

I'm still trying to get my head around how selectors and dynamic typing work in objective c. I'm essentially trying to implement this method (shown below in python/pseudocode, same thing really :P)
def isInArray(value, array, test):
for item in array:
if test(item) == value:
return True
return False
test = lambda obj: obj.property
My intended objective-c code was:
+ (BOOL)value:(id)value:
isInArray:(NSMutableArray *)array
usingSelector:(SEL)selector {
for (id item in array) {
if (value == [item selector]) {
return YES;
}
}
return NO;
}
However, this throws a compile error, complaining that I"m comparing two pointer types (id and SEL) in the if statement.
Shouldn't that if statement be comparing the object value with the object returned from running SEL selector on the object arritem? In other words, why does it think that it's comparing an object with a SEL (I don't see what would be returning a SEL there)
Checkout performSelector in NSObject. The conditional would look like,
if (value == [item performSelector:selector])
By using Key Value Coding (KVC), your code could become even simpler. Say you have an array named people, where each person is an object of the class Person. A Person has firstName and lastName properties, and you want to get all people whose first name matches "John" from the people array. You could get an array of all first names, and then lookup the name "John" in that array.
NSArray *firstNames = [people valueForKey:#"firstName"];
if ([firstNames containsObject:#"John"]) {
..
}
Even though we're using valueForKey, it's actually making a call to the firstName method, and not directly accessing the value.
at least one mistake I see in method definition. There are no need to add colon after variable name (in your case value). And you should use performSelector: to make object check with your selector.
+ (BOOL)value:(id)value
isInArray:(NSMutableArray *)array
usingSelector:(SEL)selector {
for (id item in array) {
if ([item performSelector:selector] == value) {
return YES;
}
}
return NO;
}
In some cases you will need use isEqualTo: or isEqualToString: (if you work with strings) instead of == because == will return true if it's the same object.
Also, if you will need just check presence of some object in NSArray, than you could use containsObject: (return true if it is) and indexOfObjectPassingTest: (return index of object or 'NSNotFound')

How to log arguments in runtime?

I created a new function to log every method from a class in runtime.
The problem that I have is in this line :
id value = va_arg(stackFrame, id);
Doesn't convert the type of object inside the arguments.
Any idea on what I'm doing wrong? Is there another way to do this?
void DebugArguments ( id self, SEL _cmd,...)
{
id receiver = self;
SEL receiverSelector = _cmd;
va_list stackFrame;
va_start(stackFrame, _cmd);
NSMethodSignature *signature
= [receiver methodSignatureForSelector:receiverSelector];
NSUInteger count = [signature numberOfArguments];
NSUInteger index = 2;
for (; index < count; index++)
{
id value = va_arg(stackFrame, id);
if (!value)
{
NSLog(#"Arguments: %#",value);
}
}
va_end(stackFrame);
}
I call the function InitDebug from a class like this : -
(void)MyTest:(NSString *)string {
InitDebug(self, _cmd); } I hope
to log the Argument string from the
method MyTest.
In the future, it is helpful to show all the code.
In any case, you can't do that; when you call InitDebug(...), you are pushing a new frame onto the stack and va_arg() will decode in the context of that frame, not the surrounding frame. Nor can you "go up the stack" and start grubbing about in the arguments of the calling frame as there is no guarantee that the calling frame is even preserved at that point.
If you really want to do something like this, you would probably want to subclass NSProxy and use it is a proxy between the caller and whatever object you want to log. You could then leverage the built-in forwarding mechanism of Objective-C to muck about with the arguments.

convert id into enum using objective-c

I am trying to implement a simple method, however I am still quite a newbie on objective-c.
I have this simple method which is trying to convert from an id to a specific value in enum, if matched.
This is the enum
typedef enum {
DXTypeUnknown = 0,
DXDatasource = 1,
DXGroup = 2
} DXPropertyType;
And this is the relevant method:
-(DXPropertyType)typeFromObject:(id)_type {
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return [NSNumber numberWithInt:DXDatasource];
case DXGroup:
return [NSNumber numberWithInt:DXGroup];
default:
return [NSNumber numberWithInt:DXTypeUnknown];
}
}
The very first check I would to implement is if the id can be converted to an int, then see if it falls in the two relevant categories group or datasource, or return a default value if not. Could you tell me if the switch/case I implemented is a proper solution or not ?
I would like also this method not to causing crash of an application, so what could be advisable to check, keeping in mind that in any case the default value is to be returned.
thanks
[EDIT]
I forgot to say that this value is going to be stored in a field of a NSManagedObject, which by CoreData restriction can be an NSNumber, so probably there's a better solution instead of an enum.
It might be a good idea to include this code to check if the id can be used:
if (![_type respondsToSelector:#selector(intValue)])
return nil;
However, if you'll always pass a NSNumber go ahead and declare the method as:
- (DXPropertyType)typeFromObject:(NSNumber)_type;
In your code, you're returning a NSNumber. I don't think that's what you really
want, as you'd be doing nothing with the NSNumber passed. Return the enum
item:
-(DXPropertyType)typeFromObject:(id)_type {
if (![_type respondsToSelector:#selector(intValue)])
return nil;
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return DXDatasource;
case DXGroup:
return DXGroup;
default:
return DXTypeUnknown;
}
}
And then this can be simplified to:
- (DXPropertyType)typeFromObject:(id)_type {
if ([_type respondsToSelector:#selector(intValue)]) {
int t = [_type intValue];
DXPropertyType property_t;
if (t >= 1 && t <= 2)
property_t = t;
else
property_t = DXTypeUnknown;
return property_t;
}
return nil;
}
Your switch statement is a good solution and will not cause a crash.
However, your method returns a NSNumber when it expects a different return. I suggest changing the method to
-(NSNumber)typeFromObject:(id)_type
You specify that your method returns an enum, but you return objects. So either return the enum values or specify the return type to be NSNumber *.
A different solution could be using singleton objects instead of an enum, but that's probably more work than it's worth. Think [NSNull null].

Objective C Function Question

Hey guys, check this out. I have a function that treats for me a string. No matter what it does, i just want to knwo if is possible to this function return the result for the place that it was executed. I mean, check this:
[self priceFormat:#"1"];
priceLabel.text = price;
-(void) priceFormat:(NSString*)price {
price = #"2";
}
I just want to my function treats the string and return it to the same place that it was executed.
Thanks!
Three ways to do this
Way one, using a pointer
- (void)priceFormat:(NSString **)price {
*price = #"2";
}
Wat two, using an instance variable
What you might want instead is an ivar. In the interface (most often the h file) of your class:
NSString *price;
and in the implementation (the m or mm file):
- (void)priceFormat:(NSString *)price {
price = #"2";
}
I have created an example of this here.
If you want the price to be available to other objects as well (not just self), you might want to create a property for it and synthesize it. Then use self.price = #"2"; instead. More on this here: http://MacDeveloperTips.com/objective-c/objective-c-properties-setters-and-dot-syntax.html
Just make sure you make it a copy property (NSString in use)!
Way three using return
Note, that you can also return directly from a method:
- (NSString *)priceFormat:(NSString *)price {
return #"2";
}
priceLabel.text = [self priceFormat:#"1"];