Let's say I have an uninitialized variable:
UIViewController *vc;
From this variable, I want to reference UIViewController, such that I could call alloc or new on it to return an instantiated object.
Essentially, I want to do:
UIViewController *vc = [*typeof(vc) new];
... which does not compile because the compiler expects an expression rather than a type.
If #encode returned the actual type, I could do something like:
UIViewController *vc = [NSClassFromString(#(#encode(*typeof(vc)))) new];
...however, #encode returns '#', which just means "generic object".
I realize it's a little philosophical in nature, but I get tired of typing and would like to make a macro like NEW(x). I also realize a similar macro could be made if it involves the actual declaration, but I am not satisfied with that syntax/angle.
Here's what I have... it doesn't seem ideal, since it makes a performance hit. Still looking for better answers.
static Class classFromEncoding(const char *encoding) {
char *className = strndup(encoding + 1, strchr(encoding, '=') - encoding - 1);
Class retval = NSClassFromString(#(className));
free(className);
return retval;
}
#define NEW(variable) [classFromEncoding(#encode(typeof(*variable))) new]
Here's a macro-only version:
#define CLASS_FROM_VARIABLE(variable) \
^(const char *encoding) { \
char *className = strndup(encoding + 1, strchr(encoding, '=') - encoding - 1); \
Class retval = NSClassFromString(#(className)); \
free(className); \
return retval; \
}(#encode(typeof(*variable)))
#define NEW(variable) [CLASS_FROM_VARIABLE(variable) new]
#define ALLOC(variable) [CLASS_FROM_VARIABLE(variable) alloc]
Variations can be made using objc_getClass() or NSString initWithBytes, perhaps with performance gains. Still, it's not a no-op, which is what I'd prefer.
It's [obj class] or [obj className] depending on your needs.
Related
I'm looking a macro to detect if a variable is an object or a primitive in Objective-C.
In this context, I know the parameter must be a variable and will never be an expression.
Here is the best thing I've come up with:
#define IS_OBJECT(x) ( #encode(__typeof__(x))[0] == '#' )
#define IS_PRIMITIVE(x) ( !IS_OBJECT(x) )
Usage:
NSString *testString = #"test";
NSString *nilString = nil;
NSInteger testInteger = 1;
STAssertTrue(IS_OBJECT(testString), #"IS_OBJECT(testString) must be YES");
STAssertTrue(IS_OBJECT(nilString), #"IS_OBJECT(nilString) must be YES");
STAssertFalse(IS_OBJECT(testInteger), #"IS_OBJECT(testInteger) must be NO");
There must be a better way.
Update
Considering #ChrisDevereux comment, I updated the IS_OBJECT macro.
#define IS_OBJECT(x) ( strchr("##", #encode(__typeof__(x))[0]) != NULL )
It now passes:
NSString *testString = #"test";
NSString *nilString = nil;
NSInteger testInteger = 1;
Class classTest = [NSString class];
STAssertTrue(IS_OBJECT(testString), #"IS_OBJECT(testString) must be YES");
STAssertTrue(IS_OBJECT(nilString), #"IS_OBJECT(nilString) must be YES");
STAssertFalse(IS_OBJECT(testInteger), #"IS_OBJECT(testInteger) must be NO");
STAssertTrue(IS_OBJECT(classTest), #"IS_OBJECT(classTest) must be YES");
I still don't like this answer, and hope there is something slicker. Is there something in the runtime library which does this?
Here's another way using C11's generic selection mechanism. _Generic is standard (modern) C and supported in clang for a while.
#define IS_OBJECT(T) _Generic( (T), id: YES, default: NO)
It feels a bit less runtime-ish to me so I prefer it over the #encode way. But to be honest I just used it for this answer because I love the power _Generic gives to macros and think more people should start using it. If you don't know it you should read Robert Gamble's article linked above.
Could I parse an NSString to have it call a method that matches its name in Objective-C? Here is an example:
If I had a string called doSomething, and I had a method called -(void) doSomething, could I do something like scanf to parse whatever text I typed in to check if there were any method matching it, and if yes then call that method?
Try NSSelectorFromString(#"methodName"); and the associated NSObject methods like respondsToSelector:
jxpx777's answer will give you the information you were looking for, but in case you want more, the runtime has a long list of C functions that provide a fairly complete introspection of objects and classes.
For instance, if you want an NSArray of method names implemented by a class, you can do something like this:
Class myClass = [self class];
unsigned int methodCount;
Method *methods = class_copyMethodList(myClass, &methodCount);
NSMutableArray *methodNames = [NSMutableArray arrayWithCapacity:10];
for (int i = 0; i < methodCount; i++) {
const char *methodNameCStr = sel_getName(method_getName(methods[i]));
NSString *methName = [NSString stringWithCString:methodNameCStr
encoding:NSASCIIStringEncoding];
[methodNames addObject:methName];
}
free(methods);
NSLog(#"Methods: %#", methodNames);
You will notice that plain C calls and Objective-C/Cocoa are mixed freely.
The following code compiles and runs fine (note the sel_registerName("+")):
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
#interface Integer : NSObject
{
NSInteger intValue;
}
#property (assign) NSInteger intValue;
#end
#implementation Integer
#synthesize intValue;
- (id) plus:(Integer*)anInteger
{
Integer* outInt = [Integer new];
[outInt setIntValue: intValue + [anInteger intValue]];
return outInt;
}
#end
int main (int argc, char const *argv[])
{
id pool = [[NSAutoreleasePool alloc] init];
SEL plusSel = sel_registerName("+");
Method m = class_getInstanceMethod([Integer class], #selector(plus:));
class_addMethod([Integer class], plusSel, method_getImplementation(m), method_getTypeEncoding(m));
Integer* i4 = [Integer new];
Integer* i20 = [Integer new];
[i4 setIntValue: 4];
[i20 setIntValue: 20];
Integer* res = objc_msgSend(i4, plusSel, i20);
NSLog(#"%d + %d = %d", [i4 intValue], [i20 intValue], [res intValue]);
// >> 4 + 20 = 24
[pool drain];
return 0;
}
Other than "yuck", are there reasons to be cautious about doing this?
The API to the ObjC runtime is unlikely to change, but the validity of calling sel_registerName("+") might. I've monkeyed around in the ObjC runtime a lot, and haven't run into any problems even after many updates. That being said, I wouldn't base a multimillion dollar business on this continuing to work forever.
Currently, the Objective-C runtime library doesn't perform any checks on the content of the string you are trying to register and it's unlikely that the development team change that behavior. If it is a non-empty C string, if you always use objc_msgSend to send messages for that selector and if you don't try to do something like [i4 +:i20] (which is going to cause a compiling error), there is no reason to be afraid.
Registered Objective-C selectors are actually C strings stored internally by the runtime system. The runtime system keeps a table of pointers to C strings, the so-called SEL set. When you call sel_registerName the ObjC runtime system calls strcmp for your string and for each C string stored in the SEL set. If any of the C strings in the SEL set is equal to the one you want to register, the function returns the address of the corresponding C string in the set. Otherwise, the system duplicates your string (with strdup), stores the resulting pointer in the SEL set and returns it. This new pointer becomes a new unique selector.
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
I've looked at this over and over again and I can't see the problem. Its probably obvious and I'm probably being an idiot and I apologize in advance for this.
In my interface I have:
#interface PolygonShape : NSObject
{
int numberOfSides;
int minimumNumberOfSides;
int maximumNumberOfSides;
}
#property int numberOfSides, minimumNumberOfSides, maximumNumberOfSides;
// class methods
+ (float)getAngleInDegrees:(PolygonShape *) polyshape;
+ (float)getAngleInRadians:(PolygonShape *) polyshape;
+ (NSString)getName:(PolygonShape *) polyshape;
//instance methods
- (id)init;
- (id)initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min
maximumNumberOfSides:(int)max;
#end
The part in the implementation that I get errors is for the getName method:
#implentation...
+ (NSString)getName:(PolygonShape *) polyshape
{
// here is where I get the "error: can not use an object as parameter to a method"
int sides = [polyshape numberOfSides];
NSString * s = [NSString init];
switch (sides) {
case 3:
s = "#Triangle";
// there's also an "assignment from incompatible pointer type" warning...but its secondary
break;
case 4:
return "#Square";
break;
default:
break;
}
}
The thing that drives me batty is that the class methods works just fine:
+ (float)getAngleInDegrees:(PolygonShape *) polyshape;
+ (float)getAngleInRadians:(PolygonShape *) polyshape;
Your getName: method should return (NSString *), not (NSString). I assume this is the error; if so, then yes, the error message could definitely have been more informative.
In fact, in Objective-C you will never see objects getting passed around without their * behind them, not as return values, not as parameters, not as local variables and not as member variables.
BTW, the warning you mention is because you have a typo, mixing up "#foo" with #"foo". The latter is an Objectice-C string literal, the former is a C string literal whose first character just happens to be #.
In addition to the other answers, you're using [NSString init] where you should be using [[NSString alloc] init]. However, that will only allocate an empty string, so you'd probably be better off initializing s to either #"" or nil.
I think the error is slightly misleading in this case. In Objective-C, it's generally not possible to pass an object by value (including return values). In this case, you declare the return value as NSString rather than NSString*. The declaration should be:
+ (NSString*)getName:(PolygonShape *) polyshape
not
+ (NSString)getName:(PolygonShape *) polyshape