Variadic args. How to pass variadic args to a method - iphone

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];

Related

How to skip objects while iterating in a ruby like Map method for obj-c

Using the answer here this method achieves something similar to ruby's map in obj-c:
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[result addObject:block(obj, idx)];
}];
return result;
}
my question is how can i skip an object if something wrong happens while applying the block? Typically to skip something in an enumerator one just use the return command, however that's not an option in the method above, since the block is expected to return something.
In this example I use return to skip but get an error:
NSArray *mappedArray = [objArray mapObjectsUsingBlock:^(id obj, NSUInteger i) {
// i don't want this obj to be included in final array
// so I try to skip it
return; // ERROR:incompatible block pointer types sending
// 'void(^)(__strong id, NSUInteger)' to parameter of type
// 'id(^)(__strong id, NSUInteger)'
// else do some processing
return soupedUpObj;
}];
My current way of working around it is simply returning a null object, then removing them out from the final array. But I'm sure there must be a better way than that.
If the implementation is similar to what you showed above, it would make sense to just apply the block result to an intermediate value and then check it before adding it to the result array. Something like this:
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id blockResult = block( obj, idx );
if( result != nil ){
[result addObject:blockResult];
}
}];
return result;
}
One quick remedy: Write your own variant, which uses an NSPointerArray rather than NSArray. NSPointerArray can hold nil. Then order can be preserved, and you may use nil to indicate error (assuming NSNull is a valid value which cannot be used to indicate error). With the NSPointerArray, your block would just return nil.
This is simply a limitation of whatever framework or library mapObjectsUsingBlock: comes from (it's not a standard Apple API).
Implementing array map functionality is not difficult however, so you can easily write your own version which handles nil return values from the block argument.

Objective C method with multiple NSStrings as argument

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];

objective c: passing a char argument to function crashes the app

I wrote a singleton called SomeValues where I initialize a foo NSMutableArray. I then tried to write a function SetBFSV to set the values of this array from different control views.
#interface SomeValues : NSObject {
NSMutableArray *foo;}
+ (SomeValues *) sharedInstance;
#implementation
...
- (void) SetBFSV:(char)lbl ToVal:(long)BFSvl{
NSNumber *ValueBFSvl = [NSNumber numberWithLong:BFSvl];
NSString *Strlbl = [[NSString alloc] stringWithFormat:#"%s",lbl];
[foo setValue:ValueBFSvl forKey:Strlbl];
}
I know that setValue requires a NS object for both the value and the key, but I cannot declare my function as
(void) SetBFSV:(NSString)lbl ToVal:(NSNumber)BFSvl
because it doesn't compile with the error: "Can not use an object as parameter to a method".
In one ControlView I wrote then this piece of code:
SomeValues *myBFSV = [SomeValues sharedInstance];
const unsigned char *Bar = (unsigned char *)[#"Label1" UTF8String];
NSLog(#"The string %s", Bar);
[myBFSV SetBFSV:Bar ToVal:2.5];
When compiling I get a warning on the last line:
warning: passing argument 1 of 'SetBFSV:ToVal:' makes integer from pointer without a cast
Which integer? I'm getting stupid looking around for it. When running I get the print out from the NSLog, but right afterwards the program obviously crashes with this error:
'NSInvalidArgumentException', reason: '-[NSPlaceholderString stringWithFormat:]: unrecognized selector sent to instance 0x4e03280'
Clearly I'm passing something wrong to stringWithFormat but I cannot understand what.
Thanks for any help. Have a nice day!
/luca
Possible problems with your code (unless you have typos in it):
- (void) SetBFSV:(char)lbl ToVal:(long)BFSvl function expects char as its 1st parameter but you pass it a char* - your 1st warning probably comes from here
stringWithFormat is a class method so your code should look either:
NSString *Strlbl = [[NSString alloc] initWithFormat:#"%s",lbl];
[foo setValue:ValueBFSvl forKey:Strlbl]
or
NSString *Strlbl = [NSString stringWithFormat:#"%s",lbl];
[foo setValue:ValueBFSvl forKey:Strlbl]
since you try to use -stringWithFormat instead of +stringWithFormat you get a crash
If you pass char to a function then correct format specifier for it will be %c, not %s
You probably must release your Strlbl variable if you create it with alloc/init otherwise it will leak (but you must not release it if you use +stringWithFormat:)
For one, you can't use an object as a paramter:
(void) SetBFSV:(NSString)lbl ToVal:(NSNumber)BFSvl
You need to pass them as pointers, using the asterisk.
(void) SetBFSV:(NSString*)lbl ToVal:(NSNumber*)BFSvl
Furthermore, if you need to pass them as (char) and (long) your bit of code:
SomeValues *myBFSV = [SomeValues sharedInstance];
const unsigned char *Bar = (unsigned char *)[#"Label1" UTF8String];
NSLog(#"The string %s", Bar);
[myBFSV SetBFSV:Bar ToVal:2.5]; // in either case, this is a long. why are you passing a double here?
Passes *Bar as a pointer. You should really read up on the pointers and objects. IF you need to pass them in as (const char*) and (long) do it like this:
- (void) SetBFSV:(const char*)lbl ToVal:(long)BFSvl{
NSNumber *ValueBFSvl = [NSNumber numberWithLong:BFSvl];
NSString *Strlbl = [NSString stringWithUTF8String: lbl]; // your old code had a memory leak here. you need to either create an autorelease object, or release it after adding to `foo`
[foo setValue:ValueBFSvl forKey:Strlbl];
}
My Recommendation is to do the following:
- (void) SetBFSV:(NSString*)key ToVal:(long)val{
NSNumber *value = [NSNumber numberWithLong:val];
[foo setValue:value forKey:key];
}
And call it like the following (from your example):
SomeValues *myBFSV = [SomeValues sharedInstance];
NSString *Bar = [NSString stringWithString:#"Label1"];// this may not be necessary...
NSLog(#"The string %#", Bar);
[myBFSV SetBFSV:Bar ToVal:25]; // don't pass a decimal for a (long)

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];

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.