Objective C method with multiple NSStrings as argument - iphone

I need to make init method with multiple NSStrings as argument.
Assume it looks like:
'-(id) initWithSomething: (NSString *) things, nil;'
How to recognize number of strings and write them into array ?
Regards

Use a variadic method:
//Interface
-(id) initWithSomething:(NSString*) arg1, ...;
//Implementation
-(id) initWithSomething:(NSString*) arg1, ... {
va_list args;
va_start(args, firstObject);
id obj;
for (obj = firstObject; obj != nil; obj = va_arg(args, id))
//Do stuff with each object.
va_end(args);
}

What you want is a Variadic function.

Basic objective-C syntax is as follows:
-(type)methodNameTakesInput:(type)param1 andMoreInput:(type)param2
So you can do
-(id)initWithString:(NSString *)str andOtherThing:(NSObject *)obj
Alternately, you could just pass the array you want:
-(id)initWithStuff:(NSArray *)arrayOfStuff
and fill the array as you normally would:
NSArray *arrayOfStuff = [NSArray arrayWithObjects:#"Strings!", #"More strings!", nil];

Related

Variadic args. How to pass variadic args to a method

I have created a method that accept variadic arguments like
- (NSDictionary *) getImagePixelsAtLocation: (int) locations,...NS_REQUIRES_NIL_TERMINATION
but when I send message to this class method, the value of locations variable in called method is 0 (it does not matter how many arguments I pass).
The method receives scalar data types. My question is: Can we pass scalar variable to a method as variadic arguments? If yes, what am I doing wrong?
The method definition is:
- (NSDictionary *) getImagePixelsAtLocation: (int) pixel1,...
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
va_list args;
va_start(args, pixel1);
//processing logic
va_end(args);
}
This is how I am sending message:
[HSImageProcessing getImagePixelsAtLocation:1,2,nil];
Actually, there is one glaringly obvious flaw in your code there: when a variadic function is nil-terminated, the accepted type must be an object. You cannot compare an int to nil or NULL, or [NSNull null], which defeats the purpose of nil-termination, and effectively defeats all chances of iteration using the standard for and for-in loops. In addition, NSDictionary isn't too happy about storing non-object types, and will happily make the compiler complain. I've rewritten it to accept NSNumber*, and output a dictionary of numbers.
- (NSDictionary*) getImagePixelsAtLocation: (NSNumber*) pixel1,...
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
va_list args;
va_start(args, pixel1);
//processing logic
for (NSNumber* arg = pixel1; arg != nil; arg = va_arg(args, NSNumber*)) {
[dict setObject:arg forKey:[NSString stringWithFormat:#"%i", [arg intValue]]];
}
va_end(args);
return dict;
}
All it takes is a little bit of extra code on your part to get that very same call working as well:
[self getImagePixelsAtLocation:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2],nil];

How to write a function or macro for "oneOf" ... casting in variadics?

Note - using variadic arguments, Phix has provided a super solution below. However it gives compiler warnings when used with integers since you're casting them to an id, and it will not work with for example floats.
Question -- how write a facility that chooses one item from a list? Ideally, it would be as flexible as possible regarding the type of the items/objects/whatever in the list.
Example usage:
NSInteger openingMap = [utilities oneOf:1, 2, 3, 7, 8];
initialAngle =oneOf(1.25, 1.75, 1.95, 2.00, 2.01);
self.spaceShipNickName =oneOf(#"Blaster",#"Blitzen",#"Stardancer",#"Quantum");
self.chassisProperty = oneOf(titanium, neutronium, unavailablium);
[fireworksLayer paintStars:oneOf(blue,green,white) howMany:oneOf(20,25,50)];
[trump chooseDefaultSuite:oneOf(diamonds,hearts,clubs,spades)];
// normally have a few explosions, but, occasionally have a huge display...
explosionCount = oneOf( 2,2,2,3,4,1,28,3,3,3,70 );
Note that some examples are integers, some enums, some NSStrings, etc. So again, it would be most impressive if the one facility could handle different types. (Rather than perhaps a related group, like ... oneOfIntegers, oneOfStrings, oneOfObject, whatever.)
It goes without saying that to choose the random item, just use...
= arcrandom() % number-of-items
You could use an Objective C method or class, a c function, or some sort of system of macros, category extension to NSMutableArray, or indeed blocks - or anything else.
It should be as flexible as possible for broad use anywhere in a typical iOS or Mac project... any ideas?
#define NUMARGS(...) (sizeof((id[]){__VA_ARGS__})/sizeof(id))
#define ONEOF(...) (oneOf(NUMARGS(__VA_ARGS__), __VA_ARGS__))
id oneOf(int numargs, ...) {
va_list ap;
va_start(ap,numargs);
int i = arc4random() % numargs;
id val = nil;
do {
val = va_arg(ap, id);
} while (i--);
va_end(ap);
return val;
}
Usage:
NSLog(#"%#", ONEOF(#"Blaster",#"Blitzen",#"Stardancer",#"Quantum"));
NSLog(#"%d", ONEOF( 2,2,2,3,4,1,28,3,3,3,70 ));
Note, that both work, however the latter throws some compiler warnings.
I'd put it as a category on NSArray (warning - this code is untested and probably has off-by-one errors in!)
#interface NSArray (one_of)
- (id)anyObject;
#end
#implementation NSArray (one_of)
- (id)anyObject {
if (0 == [self count]) return nil;
if (1 == [self count]) return [self objectAtIndex:0];
return [self objectAtIndex:(arcrandom() % [self count])];
}
#end
Then, use it like :
NSString *thingy = [[NSArray arrayWithObjects:#"1", #"2", #"3"] anyObject];
NB To handle numbers (and other native types etc) you must make them objects i.e.
NSInteger number = [[[NSArray arrayWithObjects:
[NSNumber numberWithInteger:1],
[NSNumber numberWithInteger:2],
[NSNumber numberWithInteger:3], nil] anyObject] intValue];

Assigning the value of a method to a variable

In Objective-C, how do I assign the return value of a method to a variable of the same type?
Are you having any problem with:
- (NSString *)getString {
return #"hehe";
}
NSString *myString = [self getString];
You need to put the method getString above the assigning or defining the getString in the interface
Say your method prototype looked like this:
- (NSString *) name;
You'd create a variable to hold the return value like this:
NSString *some_name = [obj name];
Suppose you method returns an array
-(NSMutableArray *)getArray;
then NSMutableArray *arr=[self getArray];
or if a function return an object of a class
-(Class *)getObjectOfClass;
then you can hold this object like
Class *objClass=[self getObject];
means by simply assigning the return value to same type of variable.you can access that.

method with 2 return values

I want to call a method which returns two values
basically lets say my method is like the below (want to return 2 values)
NSString* myfunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return myString;
return myString2;
}
So when i call it, i would use??
NSString* Value1 = [self myfunc:mystring];
NSString* Value2 = [self myfunc:mystring2];
I guess im doing something wrong with it, can anyone help me out?
Thanks
You can only return 1 value. That value can be a struct or an object or a simple type. If you return a struct or object it can contain multiple values.
The other way to return multiple values is with out parameters. Pass by reference or pointer in C.
Here is a code snippet showing how you could return a struct containing two NSStrings:
typedef struct {
NSString* str1;
NSString* str2;
} TwoStrings;
TwoStrings myfunc(void) {
TwoStrings result;
result.str1 = #"data";
result.str2 = #"more";
return result;
}
And call it like this:
TwoStrings twoStrs = myfunc();
NSLog(#"str1 = %#, str2 = %#", twoStrs.str1, twoStrs.str2);
You need to be careful with memory management when returning pointers even if they are wrapped inside a struct. In Objective-C the convention is that functions return autoreleased objects (unless the method name starts with create/new/alloc/copy).
You have a few options:
NSArray: Just return an array. Pretty simple.
Pointers: Pass in two pointers, and write to them instead of returning anything. Make sure to check for NULL!
Structure: Create a struct that has two fields, one for each thing you want to return, and return one of that struct.
Object: Same a structure, but create a full NSObject subclass.
NSDictionary: Similar to NSArray, but removes the need to use magic ordering of the values.
As you can only return one value/object, maybe wrap them up in an array:
-(NSArray*) arrayFromMyFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString,myString2,nil];
}
You can then use it like this:
NSArray *arr = [self arrayFromMyFunc];
NSString *value1 = [arr objectAtIndex:0];
NSString *value2 = [arr objectAtIndex:1];
You could pass results back by reference, but this is easy to get wrong (syntactically, semantically, and from memory management point of view).
Edit One more thing: Make sure that you really need two return values. If they are quite independent, two separate function are often the better choice - better reusabilty and mentainable. Just in case you are making this as a matter of premature optimization. :-)
You can only directly return one value from a function. But there is a way of doing it.
-(void) myfuncWithVal1:(NSString**)val1 andVal2:(NSString**)val2
{
*val1 = #"MYDATA";
*val2 = #"MYDATA2";
}
Then to call it outside the method you'd use:
NSString* a;
NSString* b;
[self myfuncWithVal1:&a andVal2:&b];
void myfunc(NSString **string1, NSString **string2)
{
*string1 = #"MYDATA";
*string2 = #"MYDATA2";
}
...
NSString *value1, *value2;
myfunc(&value1, &value2);
Remember that you need to pass a pointer to a pointer when working with strings and other objects.
Wrap the two strings in an NSArray:
- (NSArray*)myFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString, myString2, nil];
}
NSArray *theArray = [self myFunc]
NSString *value1 = [theArray objectAtIndex:0];
NSString *value2 = [theArray] objectAtIndex:1];
I see everyone has mentioned an NSArray but I'd go with an NSDictionary so the values don't have to be added in order or even at all. This means it is able to handle a situation where you only want to return the second string.
- (NSDictionary*)myFunction {
NSString *myString1 = #"string1";
NSString *myString2 = #"string2";
return [NSDictionary dictionaryWithObjectsAndKeys: myString1, #"key1", myString2, #"key2", nil];
}
NSDictionary *myDictionary = [self myFunction]
NSString *string1 = [myDictionary objectForKey:#"key1"];
NSString *string2 = [myDictionary objectForKey:#"key2"];

how to pass variable arguments to another method?

i have googled and came to know that how to use the variable arguments. but i want to pass my variable arguments to another method. i m getting errors. how to do that ?
-(void) aMethod:(NSString *) a, ... {
[self anotherMethod:a];
// i m doing this but getting error. how to pass complete vararg to anotherMethod
}
AFAIK ObjectiveC (just like C and C++) do not provide you with a syntax that allows what you directly have in mind.
The usual workaround is to create two versions of a function. One that may be called directly using ... and another one called by others functions passing the parameters in form of a va_list.
..
[obj aMethod:#"test this %d parameter", 1337);
[obj anotherMethod:#"test that %d parameter", 666);
..
-(void) aMethod:(NSString *)a, ...
{
va_list ap;
va_start(ap, a);
[self anotherMethod:a withParameters:ap];
va_end(ap);
}
-(void) anotherMethod:(NSString *)a, ...
{
va_list ap;
va_start(ap, a);
[self anotherMethod:a withParameters:ap];
va_end(ap);
}
-(void) anotherMethod:(NSString *)a withParameters:(va_list)valist
{
NSLog([[[NSString alloc] initWithFormat:a arguments:valist] autorelease]);
}
You cannot pass variadic arguments directly. But some of these methods provide an alternative that you can pass a va_list argument e.g.
#include <stdarg.h>
-(void)printFormat:(NSString*)format, ... {
// Won't work:
// NSString* str = [NSString stringWithFormat:format];
va_list vl;
va_start(vl, format);
NSString* str = [[[NSString alloc] initWithFormat:format arguments:vl] autorelease];
va_end(vl);
printf("%s", [str UTF8String]);
}
Have you considered setting up your arguments in either an array or dictionary, and coding conditionally?
-(void) aMethodWithArguments:(NSArray *)arguments {
for (id *object in arguments) {
if ([object isKindOfClass:fooClass]) {
//handler for objects that are foo
[self anotherMethod:object];
}
if ([object isKindOfClass:barClass]) {
//and so on...
[self yetAnotherMethod:object];
}
}
}
I think you could use macros to achieve same thing.
Let's say you wanna pass aMethod's variable arguments to another
-(void) aMethod:(NSString *) a, ... {
}
You could define your another 'method' using macro though it is not a real method:
#define anotherMethod(_a_,...) [self aMethod:_a_,##__VA_ARGS__]
This is my solution.