30

I asked a similar question, but I couldn't get it working exactly. I'm building an iPhone app, and there is a method that I want called from different files. I figured the easiest way would simply be to make a method in another file, and call the method from the other files.

Here are some problems. I need to return multiple values from the method, after passing it multiple values. For example, I'm passing it: (int, int, int, string, string). And it needs to return all of those values, after they have been changed. Someone showed me this code:

- (NSDictionary *)EndOfTurn:(int)varTurns withFatness:(int)varFatness
{
    varTurns--;

    if (varTurns <= 0) {
        varFatness = varFatness - 5;
    }
    else {
        varFatness += 2;
    }

    return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:varFatness], @"FATNESS", [NSNumber numberWithInt:varTurns], @"TURNS", nil];

}

However, this code doesn't work, and I need some more information to really understand it. Let's assuming I'm passing it these values:

int varMoney;
int varNumSheep;
int varNumShepherds;
NSString *test1;
NSString *test2;

So I need to get all of these values back from the method.

How do I declare this in the header file? This should be in an Objective-C file, but could you give me the code for the entire file so I can see where it would go with the @implementation and @end, whatnot. Also, how would I call this method?

3
  • you'll also need to retain the result of this function. Because the dictionary you're returning is from a convenience method, it is autoreleased, and will disappear shortly if not retained. i.e. when you call it you'd do something like this: NSDictionary *myDic = [self EndOfTurn:turns withFatness:fatness]; [myDic retain]; Commented Nov 7, 2009 at 7:37
  • 1
    and then [myDic release]; when you're good and done with it. Commented Nov 7, 2009 at 7:38
  • You may be interested in using container literals: your return line could be written as: return (@{@"FATNESS": @(varFatness), {@"TURNS":@(varTurns)}}); Commented Jul 21, 2021 at 18:05

6 Answers 6

71

What about passing in the values as pointers?

For example:

- (void) getValuesForInt:(int *)int1 anotherInt:(int *)int2 aBool:(BOOL *)bool1 anotherBool:(BOOL *)bool2 {
  if (*int1 == 42 && *int2 == 0) {
    *int1 = 0;
    *int2 = 42;
  }
  if (*bool1 == NO) {
    *bool2 = YES;
  }
}

Then you can invoke it like:

int int1 = 42;
int int2 = 0;
BOOL bool1 = NO;
BOOL bool2 = NO;
[self getValuesForInt:&int1 anotherInt:&int2 aBool:&bool1 anotherBool:&bool2];
NSLog(@"int1: %d int2: %d bool1: %d bool2: %d", int1, int2, bool1, bool2);
//prints "int1: 0 int2: 42 bool1: 0 bool2: 1"

Edit:

This works equally well with objects. You'll often see this used when dealing with NSError objects:

NSError *error = nil;
[anObject doSomething:foo error:&error];

Can be implemented as:

- (void) doSomething:(id)terrible error:(NSError **)error {
  if ([terrible isEqual:reallyBad]) {
    if (error != nil) { *error = [NSError errorWithDomain:@"domain" code:42 userInfo:nil]; }
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

It should be *int2 == 0. Also your NSLog line is missing the parameters.
@Wayfarer edit answer to give an example of how this would work with objects.
My grudge with this method is that one can't pass in a property because parameter as properties might not even have an underlying memory location so this is prohibited by the language. This means, besides frequently passing in nils if the return is unwanted, that if any of the secondary values need to be stored in properties then one needs to first declare a local variable and then assign that value to the property.
15

You can use a block closure to pass back multiple values from a method like this. -rrh

[self heyFunctionGiveMeBackTwoValuesFromThisFruitArray:@[@"apple", @"orange", @"banana", @"apple"] findThisFruit:@"apple" closureFunction:^(int fruitCount, NSString* fruitString)
{
    NSLog(@"Two values returned, int-fruitCount:%d, NSString-fruiteString:%@", fruitCount, fruitString);
}];

- (void)heyFunctionGiveMeBackTwoValuesFromThisFruitArray:(NSArray*)fruitsArray findThisFruit:(NSString*)findThisFruit closureFunction:(void (^)(int fruitCount, NSString *fruitString))passBackResultsUsingThisClosure
{
    NSInteger fruitsFound = 0;
    NSString* fruitsMessage = [NSString stringWithFormat:@"No %@ Found", findThisFruit];
    for (NSString* string in fruitsArray)
    {
        if ([string compare:findThisFruit] == NSOrderedSame)
        {
            fruitsFound++;
        }
    }
    if (fruitsFound > 0)
    {
        fruitsMessage = [NSString stringWithFormat:@"You have %@ on your list this many times:%d", findThisFruit, fruitsFound];
    }
    passBackResultsUsingThisClosure(fruitsFound, fruitsMessage);
}

Results: Two values returned, int-fruitCount:2, NSString-fruiteString:You have apple on your list this many times:2

Comments

11

If you have that many different things that need to be returned from a method, either encapsulate it into an NSDictionary as others have suggested or consider just defining a class. You can declare the instance variables and properties to encapsulate the data, as needed.

Defining a class to encapsulate such information proves to be quite efficient and maximizes flexibility. If you need to refactor your app such that the collection of data gains new fields, needs to be saved for later, or might need to gain functionality, a class will ease these changes.

1 Comment

This would be a good idea if you're not worried about the workload, especially for frame sensitive applications. If you're developing, let's say a VR app, every bit of saving would be a benefit, so I personally would use the DaveDeLong answer for those instances.
10

Since you can only return a single value from any method in C and C-derived languages, you simply need to return a single value that represents all of your other values. This is what your sample code is doing with an NSDictionary.

The sample code is correct, even if it's a bit contrary to common Objective-C style.

What you declare in the header file is simply the declaration of the method, that is:

@interface MyClass : NSObject
- (NSDictionary *)EndOfTurn:(int)varTurns withFatness:(int)varFatness;
@end

In the source file, then:

@implementation MyClass
// code, as given above
@end

2 Comments

Okay, what is the syntax for calling this function? I've imported the header file already, but when I try calling the function it says "Expected : Before : token. This the call: (NSDictionary *)EndOfTurn:(int)varTurns withFatness:(int)varFatness; I'm passing it the Variables "varTurns" and "varFatness"
Your call should look like [object EndOfTurn:varTurns withFatness:varFatness] - the (int) bits are just for the definition, not the call.
7

If you only need to return primitive values, then returning a struct may be the optimal solution. You get compile-time error checking (e.g. as opposed to an NSDictionary where you could attempt to read an invalid key), while not requiring all the code/files involved in creating a class.

typedef struct myStruct {
  int varMoney;
  int varNumSheep;
  int varNumShepherds;
} myStruct;

Apple uses structs in many of their methods too (e.g. CGPoint, CGRect).

The reason this won't work with objects is because ARC forbids this.

1 Comment

I'm pretty sure embedding objects in struct is not supported anymore since ARC. It's probably better to use a class or dictionary as others have mentioned, even if it is slightly less "optimal".
1

One slight improvement to the last point in some designs is to use a struct holding enum members. This gives you the compile-time checking already mentioned, something that looks like an object in the return value, and the benefit of clear cases if you need to check the values in the return.

The struct:

typedef struct _SIXRecorderStateChange {
    SIXRecorderState oldState;
    SIXRecorderState newState;
} SIXRecorderStateChange;

The client code:

    SIXRecorderStateChange stateChange = [recorderState stop];
    if (stateChange.newState == SIXRecorderStopped) {
...
...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.