Define a method that has many (or infinite) arguments - objective-c

The initWithObjects: method of NSArray takes an indefinite list of arguments:
NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:(id), ..., nil
How can I define my own method like this?
- (void)CustomMethod:????? <= want to take infinite arguments {
}

The "infinite arguments" are variable arguments and the methods that use them are called variadic methods. You define them the same way as your NSMutableArray example. Apple's Technical Q&A has an example of how to implement it.
- (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];
va_start(argumentList, firstObject); // Start scanning for arguments after 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);
}
}
The reason for the nil argument is so that you know when you have reached the end of the list. Functions like NSLog and printf do not require the last argument to be nil because it can count the number of specifiers in the format string (%d, %s etc...)

Related

Objective-C dynamic properties at runtime?

Is it possible to create an Objective-C class that can have an arbitrary number of dynamic properties at runtime?
I want to be able to call mySpecialClass.anyProperty and intercept this inside my class to be able to provide my own custom implementation that can then return an NSString (for instance) at runtime with raising an exception. Obviously this all has to compile.
Ideal would be if I could refer to my properties using something similar to the new literal syntax, e.g. mySpecialClass["anyProperty"].
I guess in a way I want to create something like a dynamic NSDictionary with no CFDictionary backing store, that executes 2 custom methods on property getting and setting respectively, with the property name passed in to these accessor methods so they can decide what to do.
There are at least two ways to do this.
Subscripting
Use objectForKeyedSubscript: and setObject:forKeyedSubscript:
#property (nonatomic,strong) NSMutableDictionary *properties;
- (id)objectForKeyedSubscript:(id)key {
return [[self properties] valueForKey:[NSString stringWithFormat:#"%#",key]];
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key {
[[self properties] setValue:object forKey:[NSString stringWithFormat:#"%#",key]];
}
Person *p = [Person new];
p[#"name"] = #"Jon";
NSLog(#"%#",p[#"name"]);
resolveInstanceMethod:
This is the objc_sendMsg executed by the runtime for all methods:
If you look at the bottom, you have the opportunity to resolveInstanceMethod:, which lets you redirect the method call to one of your choosing. To answer your question, you need to write a generic getter and setter that looks-up a value on a dictionary ivar:
// generic getter
static id propertyIMP(id self, SEL _cmd) {
return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}
// generic setter
static void setPropertyIMP(id self, SEL _cmd, id aValue) {
id value = [aValue copy];
NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];
// delete "set" and ":" and lowercase first letter
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];
[[self properties] setValue:value forKey:key];
}
And then implement resolveInstanceMethod: to add the requested method to the class.
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) hasPrefix:#"set"]) {
class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v#:#");
} else {
class_addMethod([self class], aSEL,(IMP)propertyIMP, "##:");
}
return YES;
}
You could also do it returning a NSMethodSignature for the method, which is then wrapped in a NSInvocation and passed to forwardInvocation:, but adding the method is faster.
Here is a gist that runs in CodeRunner. It doesn't handle myClass["anyProperty"] calls.
You're asking different things. If you want to be able to use the bracket syntax mySpecialClass[#"anyProperty"] on instances of your class, it is very easy. Just implement the methods:
- (id)objectForKeyedSubscript:(id)key
{
return ###something based on the key argument###
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key
{
###set something with object based on key####
}
It will be called everytime you use the bracket syntax in your source code.
Otherwise if you want to create properties at runtime, there are different ways to proceed, take a look at NSObject's forwardInvocation: method, or look at the Objective-C Runtime Reference for functions to dynamically alter a class...
Guillaume is right. forwardInvocation: is the way to go. This answer gives some more details: method_missing-like functionality in objective-c (i.e. dynamic delegation at run time)
This has even more details: Equivalent of Ruby method_missing in Objective C / iOS
And these are some other lesser known Obj-C features that might help you: Hidden features of Objective-C
Enjoy!

Variable Argument List - Memory Management Error

I am trying to create a method to quickly and easily create an NSArray from a va_list, however, when I run the method, I receive an exc_bad_access due to some bad memory management somewhere, although I cannot determine where this place is.
Please could you take a look at the code and tell me where and why this is occurring.
Thanks in advanced,
Max.
NSArray *arrayCreate(id firstObject, ...) {
NSMutableArray *objects = [NSMutableArray array];
[objects addObject:firstObject];
va_list args;
va_start(args, firstObject);
id arg;
while ((arg = va_arg(args, id))) {
[objects addObject:arg];
}
va_end(args);
return [objects copy];
}
Usage (just to test that it's working):
NSLog(#"%#", arrayCreate(#"1", #"2", #"3", #"4"));
You forgot to nil-terminate your arglist. In C, functions have no way of knowing how many variadic arguments you passed, so it's common to end a series of pointers with a null pointer (to indicate no more valid input.) Your code appears to be checking for this (arg = va_arg(args, id) will be false when it reaches nil) but your input is missing it.

Make yourself a method such as -[NSArray arrayWithObjects:]; infinite parameters separed by , ended by nil on argument

On Objective-C, I can do something like:
UIAlertView *av = [[UIAlertView alloc] initWith ... otherButtonTitles:#"button1", #"button2", nil];
Can I make a method for myself which takes as an argument these parameters separed by a comma... And if so how?
Declare the method like this in your #interface:
- (id)myObjectWithObjects:(id)firstObject, ... NS_REQUIRES_NIL_TERMINATION;
Then in the #implementation you would define it like this:
- (id)myObjectWithObjects:(id)firstObject, ...
{
va_list args;
va_start(args, firstObject);
for (id arg = firstObject; arg != nil; arg = va_arg(args, id))
{
// Do something with the args here
}
va_end(args);
// Do more stuff here...
}
The va_list, va_start, va_arg and va_end are all standard C syntax for handling variable arguments. To describe them simply:
va_list - A pointer to a list of variable arguments.
va_start - Initializes a va_list to point to the first argument after the argument specified.
va_arg - Fetches the next argument out of the list. You must specify the type of the argument (so that va_arg knows how many bytes to extract).
va_end - Releases any memory held by the va_list data structure.
Check out this article for a better explanation - Variable argument lists in Cocoa
See also: "IEEE Std 1003.1 stdarg.h"
Another example from the Apple Technical Q&A QA1405 - Variable arguments in Objective-C methods:
#interface NSMutableArray (variadicMethodExample)
- (void) appendObjects:(id) firstObject, ...; // This method takes a nil-terminated list of objects.
#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];
va_start(argumentList, firstObject); // Start scanning for arguments after 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

Method with an array of inputs

I want to have a method where I can put as many arguments as I need like the NSArray:
- (id)initWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
I can then use:
NSArray *array = [[NSArray alloc] initWithObjects:obj1, obj2, ob3, nil];
I can add as many objects as I want as long as I add 'nil' at the end to tell it I'm done.
My question is how would I know how many arguments were given, and how would I go through them one at a time?
- (void)yourMethod:(id) firstObject, ...
{
id eachObject;
va_list argumentList;
if (firstObject)
{
// do something with firstObject. Remember, it is not part of the variable argument list
[self addObject: firstObject];
va_start(argumentList, firstObject); // scan for arguments after firstObject.
while (eachObject = va_arg(argumentList, id)) // get rest of the objects until nil is found
{
// do something with each object
}
va_end(argumentList);
}
}
I think what you're talking about is implementing a variadic method. This should help: Variable arguments in Objective-C methods
I haven't had experience with these variadic methods (as they're called), but there's some Cocoa functionality to deal with it.
From Apple's Technical Q&A QA1405 (code snippet):
- (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];
va_start(argumentList, firstObject); // Start scanning for arguments after 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);
}
}
Copied from http://developer.apple.com/library/mac/#qa/qa2005/qa1405.html
I would try this: http://www.numbergrinder.com/node/35
Apple provides access in their libraries for convenience. The way to know how many elements you have is by iterating over the list until you hit nil.
What I would recommend, however, if you want to pass a variable number of arguments into some method you're writing, just pass an NSArray and iterate over that array.
Hope this helps!

Can't have an ivar with a name of description in Objective-C description method?

I'm trying to implement the Objective-C description method for my NSObject-derived object.
However, my derived object has an ivar of name description. And for some reason this is causing a crash.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"MyObject.description = %#\n", self.description];
return output;
}
Why would this be an issue?
Short Answer: The crash is a result of a stack overflow because your -description method calls itself repeatedly. To do what you want to do (accessing the ivar from within the description method), you should not use the prefix self. in front of the ivar.
More Detail:
In Objective-C, self.description is shorthand for [self description]. Using the dot-syntax informs the compiler that you want to access a property named description, and not the ivar itself.
It's an issue because you're creating an infinite loop. self.description will call [self description], which is exactly the method you're within. Hence you have the method calling itself repeatedly.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"super's description = %#\n", [super description]];
[output appendFormat:#"MyObject.description = %#\n", description];
return output;
}
You can access the instance variable directly, rather than using self.description. Also, I added an extra line to show how you can call super's description method (which doesn't create an infinite loop).