Hee
Does anybody know how to implement an method in objective c that will take an array of arguments as parameter such as:
[NSArray arrayWithObjects:#"A",#"B",nil];
The method declaration for this method is:
+ (id)arrayWithObjects:(id)firstObj...
I can't seem to make such method on my own. I did the following:
+ (void) doSometing:(id)string manyTimes:(NSInteger)numberOfTimes;
[SomeClass doSometing:#"A",#"B",nil manyTimes:2];
It will give the warningtoo many arguments to function 'doSometing:manyTimes:'
Thanks already.
The ellipsis (...) is inherited from C; you can use it only as the final argument in a call (and you've missed out the relevant comma in your example). So in your case you'd probably want:
+ (void)doSomethingToObjects:(id)firstObject, ...;
or, if you want the count to be explicit and can think of a way of phrasing it well:
+ (void)doManyTimes:(NSInteger)numberOfTimes somethingToObjects:(id)firstObject, ...;
You can then use the normal C methods for dealing with ellipses, which reside in stdarg.h. There's a quick documentation of those here, example usage would be:
+ (void)doSomethingToObjects:(id)firstObject, ...
{
id object;
va_list argumentList;
va_start(argumentList, firstObject);
object = firstObject;
while(1)
{
if(!object) break; // we're using 'nil' as a list terminator
[self doSomethingToObject:object];
object = va_arg(argumentList, id);
}
va_end(argumentList);
}
EDIT: additions, in response to comments. You can't pass the various things handed to you in an ellipsis to another function that takes an ellipsis due to the way that C handles function calling (which is inherited by Objective-C, albeit not obviously so). Instead you tend to pass the va_list. E.g.
+ (NSString *)doThis:(SEL)selector makeStringOfThat:(NSString *)format, ...
{
// do this
[self performSelector:selector];
// make string of that...
// get the argument list
va_list argumentList;
va_start(argumentList, format);
// pass it verbatim to a suitable method provided by NSString
NSString *string = [[NSString alloc] initWithFormat:format arguments:argumentList];
// clean up
va_end(argumentList);
// and return, as per the synthetic example
return [string autorelease];
}
Multiple arguments (also known as an arglist) can only come at the end of a method declaration. Your doSomething method would look something like this:
+ (void)doNumberOfTimes:(NSInteger)numberOfTimes withStrings:(id)firstArg, ...
{
va_list args;
va_start(args, firstArg);
NSString * argString = firstArg;
while (argString != nil)
{
// do something with argString here
argString = va_arg(args, NSString *);
}
va_end(args);
}
To be called as follows:
[SomeClass doNumberOfTimes:2 withStrings:#"A", #"B", nil];
See also: How to create variable argument methods in Objective-C
I think you're after a variadic function. Here's Apple's documentation: http://developer.apple.com/library/mac/qa/qa2005/qa1405.html
Related
Maybe this will be obviously simple for most of you, but could you please give an example how to create similar methods (in Objective-C) and functions in C to create functions like NSString's stringWithFormat:, or NSLog().
Just to remind:
[NSString stringWithFormat:#"example tekst %i %# %.2f", 122, #"sth", 3.1415"];
NSLog(#"account ID %i email %#", accountID, email);
I'd like to create the similar to NSString's method stringWithFormat:, NSURL - urlWithFormat.
What these are called, generally, is "variadic functions" (or methods, as it were).
To create this, simply end your method declartion with , ..., as in
- (void)logMessage:(NSString *)message, ...;
At this point you probably want to wrap it in a printf-like function, as implementing one of those from scratch is trying, at best.
- (void)logMessage:(NSString *)format, ... {
va_list args;
va_start(args, format);
NSLogv(format, args);
va_end(args);
}
Note the use of NSLogv and not NSLog; consider NSLog(NSString *, ...); vs NSLogv(NSString *, va_list);, or if you want a string; initWithFormat:arguments: on NSString *.
If, on the other hand, you are not working with strings, but rather something like
+ (NSArray *)arrayWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
things get a lot easier.
In that case, instead of a vprintf-style function, use a loop going through args, assuming id as you go, and parse them as you would in any loop.
- (void)logMessage:(NSString *)format, ... {
va_list args;
va_start(args, format);
id arg = nil;
while ((arg = va_arg(args,id))) {
/// Do your thing with arg here
}
va_end(args);
}
This last sample, of course, assumes that the va_args list is nil-terminated.
Note: In order to make this work you might have to include <stdarg.h>; but if memory serves, this gets included in connection with NSLogv, meaning it comes down by way of "Foundation.h", therefore also "AppKit.h" and "Cocoa.h", as well as a number of others; so this should work out of the box.
- (void)methodWithFormat:(NSString*)format, ... {
va_list args;
va_start(args,format);
//loop, get every next arg by calling va_arg(args,<type>)
// e.g. NSString *arg=va_arg(args,NSString*) or int arg=(args,int)
va_end(args);
}
If you want to pass the variable arguments to stringWithFormat:, use something like:
NSString *s=[[[NSString alloc] initWithFormat:format arguments:args] autorelease];
One thing to mention here is that, the first NSString parameter here comes as format, and the other are passed in the variable argument. right? So before entering the for loop, you have one parameter to handle.
- (NSString *) append:(NSString *)list, ...
{
NSMutableString * res = [NSMutableString string];
[res appendString:list];
va_list args;
va_start(args, list);
id arg = nil;
while(( arg = va_arg(args, id))){
[res appendString:arg];
}
va_end(args);
return res;
}
- (void) test_va_arg
{
NSString * t = [self append:#"a", #"b", #"c", nil];
STAssertEqualObjects(#"abc", t, #"");
}
I need to pass an IF statement to a method. In JavaScript you can assign a function to a variable. Then that variable can be passed to a function and executed. Does this exist in Objective-C?
This is the pattern I'd like to implement:
-(void)singleComparisonWith:(NSArray *)data
IndexBegin:(NSUInteger)indexBegin
IndexEnd:(NSUInteger)indexEnd
Threshold:(float)threshold {
NSIndexSet *set1 = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationConcurrent
Comparison:XXXXXXXXX];
// XXXXXXXXX is an IF statement that looks for value at an index above threshold
}
-(void)rangeComparisonWith:(NSArray *)data
IndexBegin:(NSUInteger)indexBegin
IndexEnd:(NSUInteger)indexEnd
ThresholdLow:(float)thresholdLow
ThresholdHigh:(float)thresholdHigh {
NSIndexSet *candidates = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationReverse
Comparison:YYYYYYYYY];
// YYYYYYYYY is an IF statement that looks for value at an index above thresholdLow and above thresholdHigh
}
-(NSIndexSet *)searchWithData:data
Range:(NSIndexSet *)range
Option:(NSEnumerationOptions)option
Comparison:(id)comparison {
return [data indexesOfObjectsAtIndexes:range
options:option
passingTest:^(id obj, NSUInteger idx, BOOL *stop){
// Comparison is used here. Returns YES if conditions(s) are met.
}
];
}
EDIT:
Here's the solution thanks to #Charles Srstka.
NSIndexSet *set1 = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationConcurrent
Comparison:BOOL^(id o) {
return ([o floatValue] > threshold);
}
];
-(NSIndexSet *)searchWithData:data
Range:(NSIndexSet *)range
Option:(NSEnumerationOptions)option
Comparison:(BOOL(^)(id o))comparison {
return [data indexesOfObjectsAtIndexes:range
options:option
passingTest:^(id obj, NSUInteger idx, BOOL *stop){
return comparison(obj);
}
];
No errors in that segment.
Thank you for your help.
What you want in Objective-C is called block syntax. While certainly not the nicest thing to look at, or the easiest thing to remember, it will do what you want.
// declares a block named 'foo' (yes, the variable name goes inside the parens)
NSUInteger (^foo)(NSString *) = ^(NSString *baz) {
return [baz length];
};
// now you can call foo like a function:
NSUInteger result = foo(#"hello world");
// or pass it to something else:
[someObject doSomethingWith:foo];
// A method that takes a block looks like this:
- (void)doSomethingWith:(NSUInteger (^)(NSString *))block;
This site is a handy "cheat sheet" that lists all the ways to declare a block in Objective-C. You will probably be referring to it often. The URL I linked to is a newer, work-friendly mirror. I'm sure you can guess the site's original URL if you think about it. ;-)
Basically whenever you see a ^ in Objective-C, you're looking at a block declaration. Unless, of course, you're looking at an XOR operation. But usually it's a block.
EDIT: Look at the site I linked to, where it says "as an argument to a method call." You need to declare it using that syntax, i.e.
... comparison: ^BOOL(id o) {
return ([o floatValue] > threshold);
}];
I know it's not the most intuitive syntax in the world, which is why that site is useful as a cheat sheet.
Also, unrelated to your issue, but Objective-C naming convention is to start the argument labels with lower-case letters; i.e. range:, options:, and comparison: rather than Range:, Option:, Comparison:.
I often find myself creating a "wrapper" block which just serves to execute a number of other blocks, usually with the same type signature.
Say I have 2 blocks with the same type signature:
MyBlockT block1 = ^(NSString *string, id object) {
//1 does some work
};
MyBlockT block2 = ^(NSString *string, id object) {
//2 does some other work
};
Is there some way to implement the magic function Combine() which would take 2 blocks:
MyBlockT combinedBlock = Combine(block1, block2); //hypothetical function
and be equivalent to doing:
MyBlockT combinedBlock = ^(NSString *string, id object) {
block1(string, object);
block2(string, object);
};
I know this only makes sense with blocks that return void, but that's all I'm interested in.
The Combine function needs only take in 2 blocks, if I have more I can just chain them. I'm at wits end on how to go about implementing this or whether it's even possible.
P.S. I wouldn't mind if the solution involved C macros
EDIT
I'd like to be able to use the resulting block as a method argument, e.g.:
[UIView animateWithDuration:1 animations:someCombinedBlock];
Is this what you are looking for?
MyBlockT CombineBlocks(MyBlockT block1, MyBlockT block2)
{
return [^(NSString *string, id object) {
block1(string, object);
block2(string, object);
} copy];
}
The function creates a new block that calls the two given blocks sequentially.
Now up on GitHub, WoolBlockInvocation!
This is a pair of classes, WSSBlockInvocation and WSSBlockSignature, along with some supporting code, that leverage libffi and the ObjC #encode strings which the compiler generates for Blocks to allow you to invoke a whole list of Blocks with the same set of arguments.
Any number of Blocks can be added to an invocation object, provided their signatures -- meaning return type and number and types of arguments -- match. After setting arguments on the invocation object, the Blocks can be invoked in turn, with the return values, if any, stored for later access.
The piece that you're particularly interested in, sewing that list of Blocks up into a single Block, is provided by the invocationBlock method of WSSBlockInvocation.
- (id)invocationBlock
{
return [^void (void * arg1, ...){
[self setRetainsArguments:YES];
va_list args;
va_start(args, arg1);
void * arg = arg1;
NSUInteger numArguments = [blockSignature numberOfArguments];
for( NSUInteger idx = 1; idx < numArguments; idx++ ){
[self setArgument:&arg atIndex:idx];
arg = va_arg(args, void *);
}
va_end(args);
[self invoke];
} copy];
}
This returns a Block that (ab)uses varargs functionality to defer assigning arguments until that encapsulating Block is actually invoked itself. You can thus do the following:
WSSBlockInvocation * invocation = [WSSBlockInvocation invocationWithBlocks:#[animationBlockOne, animationBlockTwo]];
void (^combinedAnimation)(void) = [invocation invocationBlock];
[UIView animateWithDuration:1 animations:combinedAnimation];
Of course, if you're just worried about Blocks for animations, that take no arguments and have no return value, constructing a wrapper Block is trivial:
void (^combinedAnimation)(void) = ^{
animationBlock();
anotherAnimationBlock();
// etc.
};
You only need my code if you need to wrap a set of Blocks and invoke them all with the same set of arguments.
N.B. I have tested this on OS X on x86_64, but not on any other platform. I hope it works on ARM under iOS, but varargs is famously "not portable" and it may not. Caveat compilor, and let me know if something breaks.
Here is a fun abuse of varargs:
id combine(id block, ...)
{
NSMutableArray *blocks = [NSMutableArray array];
//[blocks addObject:block];
va_list objlist;
va_start(objlist, block);
//while((obj = va_arg(ap, id))) { // }
for(id obj = block; obj; obj = va_arg(objlist, id)) {
[blocks addObject:[obj copy]];
}
va_end(objlist);
void (^wrapper)(id,...) = ^(id arg, ...) {
NSMutableArray *args = [NSMutableArray array];
va_list arglist;
va_start(arglist, arg);
for(id x = arg; x; x = va_arg(arglist, id)) {
[args addObject:x];
}
va_end(arglist);
for(void (^blk)() in blocks) {
blk(args);
}
};
return [wrapper copy];
}
int main() {
NSString *fmt = #"-%d-\n%#\n---";
void (^foo)() = combine(^(NSArray *a){ NSLog(fmt, 1, a); },
^(NSArray *a){ NSLog(fmt, 2, a); }, nil);
foo(#"first", #"second", nil);
return 0;
}
You must define each block to accept an NSArray of arguments, and both the combine and resulting block invocation must have at least one argument and end in nil.
If you know the method signature ahead of time, you can work around the NSArray and block arguments restriction by altering the wrapper block appropriately.
Since you don't mind macros
#define combinedBlock(string, object) \
block1((string), (object) ) \
block2((string), (object) )
if you need to perform 2 or more animations simultaneously then RZViewActions is everything you need. Its code looks like almost as animateWithDuration:... calls but with additional features.
If you need to perform ANY blocks simultaneously then you need something like ReactiveCocoa. But I suggest you PromiseKit (simply because it is easier).
I would like to pass a variable argument list from one method (functionOne) to another (functionTwo). Everything works fine, except that I have not been able to figure out how to setup the va_list in functionTwo in a way where I can access the first parameter in the va_list. Using va_arg advances to the second parameter in the va_list. Thx.
- (void)functionOne:(NSString *)configFiles, ... {
va_list args;
va_start(args, configFiles);
[self functionTwo:args];
va_end(args);
}
- (void)functionTwo:(va_list)files {
NSString *file;
while ((file = va_arg(configFiles, NSString *))) {
...
}
}
The first variadic argument is not the argument passed to va_start – it's the one immediately following it. If you want functionTwo: to have access to the configFiles string, you'll need to pass it in explicitly.
See Technical Q&A QA1405: Variable arguments in Objective-C methods.
Methods that take variable arguments are known as variadic methods.
Keep in mind that the implementation of an Objective-C method is just
a block of code, like a C function. The variadic argument macros
described in the stdarg(3) manual page work the same way in a method
as they do in an ordinary function.
Here's an example of an Objective-C category, containing a variadic
method that appends all the objects in a nil-terminated list of
arguments to an NSMutableArray instance:
#import <Cocoa/Cocoa.h>
#interface NSMutableArray (variadicMethodExample)
// This method takes a nil-terminated list of objects.
- (void)appendObjects:(id)firstObject, ...;
#end
#implementation NSMutableArray (variadicMethodExample)
- (void)appendObjects:(id)firstObject, ... {
id eachObject;
va_list argumentList;
if (firstObject) // The first argument isn't part of the varargs list,
{ // so we'll handle it separately.
[self addObject: firstObject];
// Start scanning for arguments after firstObject.
va_start(argumentList, firstObject);
while (eachObject = va_arg(argumentList, id)) // As many times as we can get an argument of type "id"
[self addObject: eachObject]; // that isn't nil, add it to self's contents.
va_end(argumentList);
}
}
#end
A solution that I use for debugging purposes is like
-(void) debug:(NSString*)format, ... {
if (level < MXMLogLevelDebug) return;
if(format == nil) return;
va_list args, args_copy;
va_start(args, format);
va_copy(args_copy, args);
va_end(args);
NSString *logString = [[NSString alloc] initWithFormat:format
arguments:args_copy];
NSString *funcCaller = #"";
NSArray *syms = [NSThread callStackSymbols];
if ([syms count] > 1) {
funcCaller = [syms objectAtIndex:1];
}
NSString *logMessage = [NSString stringWithFormat:#"%# DEBUG: %#", funcCaller, logString];
NSLog(#"%#",logMessage);
}
The side-effect that this can have is that you have to add a guard on the args to be sure is not NULL.
I am quite new to Objective-C in some ways, so I'd like to ask how should I make methods that return the objects themselves. Let me show an example:
In NSArray you can do [NSArray arrayWithObjects:bla,bla,nil];
How I make that kind of method to my own class?
There are two main things going on with that method:
It's a Class method (ie, a + method)
It uses a variable argument list
To make it, you'd probably do something like this:
+ (id)fooWithStuff:(id)stuff, ... NS_REQUIRES_NIL_TERMINATION {
// the "+" means it's a class method
// the "NS_REQUIRES_NIL_TERMINATION" is so that the compiler knows you have to use it like this:
// foo = [ThisClass fooWithStuff:thing1, thing2, thing3, nil];
// (IOW, there must be a "nil" at the end of the list)
va_list args; // declare a "variable list"
va_start(args, stuff); // the list starts after the "stuff" argument
Foo *foo = [[Foo alloc] init]; // create a Foo object
id nextStuff = stuff; // this is the next stuff
while(nextStuff != nil) { // while there is more stuff...
[foo doSomethingWithStuff:nextStuff]; // do something with the stuff
nextStuff = va_arg(args, id); // get the next stuff in the list
// the "id" means that you're asking for something the size of a pointer
}
va_end(args); // close the argument list
return [foo autorelease]; // return the Foo object
}