Getting argument values from NSInvocation - objective-c

Could someone please explain how to go about getting the values passed to a non-existant method that is being intercepted when using:
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
Given a message like:
[SomeClass doSomething:#"theThing" withSomething:#"aParam"];
I can get the method signature without a problem but I am terribly confused about how to get the values that were passed in with it.
Am I totally off base in when I should use these methods or just missing something?

-[NSInvocation getArgument:atIndex:]
So in your case, you would use it like:
__unsafe_unretained NSString * firstArgument = nil;
__unsafe_unretained NSString * secondArgument = nil;
[theInvocation getArgument:&firstArgument atIndex:2];
[theInvocation getArgument:&secondArgument atIndex:3];
NSLog(#"Arguments: %# and %#", firstArgument, secondArgument);
Remember that self and _cmd are arguments 0 and 1.

Related

Converting __block NSString to NSString (and eventually to std::string)

+(std::string)somefunc{
__block NSString *vals = nil;
[[Something somecall] completion:^(some params){vals=#"yay"}];
return std::string([vals UTF8String]);
}
This function call throws an error "-[__NSMallocBlock__ UTF8String]: unrecognized selector sent to instance 0x------"
Based on the way I've converted NSStrings in the past, I'm assuming this is something to do with my need to declare the NSString as a __block to modify it inside the []. But I could not find an answer anywhere.
What is the "best" way to convert it?

Using typecast for (void *) in Objective C

i am using the addToolTipRect: method to set a tooltip rect
- (NSToolTipTag)addToolTipRect:(NSRect)aRect owner:(id)anObject userData:(void *)userData
and method stringForToolTip: to obtain string value for tooltip.
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
However the above functions work fine if i send something like
[self addToolTipRect:someRect owner:self userData:#"Tool tip string"];
But doesn't work when i send the following string. Error: BAD_ACCESS
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
In both the cases, the stringForToolTip looks like:
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
id obj = (id)data;
NSString * str=nil;
if ([obj isKindOfClass:[SomeClass class]]) //This is my system defined class and works fine
{
SomeClass * someClassObj = (SomeClass *) data;
str = someClassObj.title;
}
else if([obj isKindOfClass:[NSString class]])
str = (NSString*)obj;
return str;
}
NOTE: In the stringForToolTip: method I also want to check for some other class example [obj isKindOF:[SomeClass class]] and i don't want to assert that value. The problem here is just in getting the string value by proper cast but I can't figure out how! Please tell me where I am going wrong?
edit:
What should be the right way to get the String value for tooltip in that case? should the point or tag be considered?
(void *) is not an object pointer.
That #"Tool tip string" worked was by coincidence based on the fact that is is a compile-time constant with a (essentially) permanent allocation and permanent address.
But in the code:
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
tooltipStr is an object that is kept in memory by a strong reference (retain count > 0). Since userData: does not handle objects it does not make a strong reference (does not increase the retain count) so it is released, will disappear soon becoming invalid.
Notes from the documentation:
The tooltip string is obtained from the owner. The owner must respond to one of two messages, view:stringForToolTip:point:userData: or description, use the latter. Note that NSString responds to description so you can pass an NSString for the value of owner. So, what you want is: [self addToolTipRect:someRect owner:tooltipStr userData:NULL];. There is still an issue that something must hole a strong reference to the NSString instance.
You can: [self addToolTipRect:someRect owner:#"Tool tip string" userData:NULL];
Probably the best way to go is to pass self as owner and NULL as data and implement the delegate method: view:stringForToolTip:point:userData: in the class.

Obj-C subclass doesn't seem to recognize inherited selector

First off, my code:
#interface Block : NSObject {
NSData *data;
NSInteger slice_count;
}
#property (readonly) NSData *data;
+ (Stopwatch *) runOldTestsUsingConfiguration:(TestConfiguration *)c;
- (Slice *) getSlice:(NSUInteger)idx;
#end
- (Slice *) getSlice:(NSUInteger)idx {
void *b = (void*)[data bytes] + idx*slice_count;
int len = [data length] / slice_count;
Slice *ret = [Slice alloc];
[ret initWithBytesNoCopy:b length:len freeWhenDone:NO];
return ret;
//NSString *temp2 = [data description];
//NSRange r = NSMakeRange(idx*slice_count, [data length] / slice_count);
//NSData *d = [data subdataWithRange:r];
//NSString *temp = [d description];
//Slice *s = [[Slice alloc] initWithBytesNoCopy:(void *)[d bytes] length:r.length freeWhenDone:NO];
//return s;
}
where Slice is a simple subclass of NSData.
For some reason I'm getting a run-time error that seems to indicate my Slice instance either a) isn't actually a concrete instance (?) or b) something is going wrong in its inheritance and the message isn't binding itself to Slice properly (almost certainly by my as yet unknown error).
The exact error I'm getting is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '***
initialization method -initWithBytes:length:copy:freeWhenDone:bytesAreVM:
cannot be sent to an abstract object of class Slice: Create a concrete instance!'
Can anyone help me out? I've tried just about everything I can think of (basic routines of which are detailed in the message call itself) and I am still coming up dry. What does it mean when it says 'create a concrete instance'? Isn't that what I'm doing when I alloc it?
Subclassing NSData is a lot more complicated than you would think. In most cases you are better off just writing a wrapper around NSData instead of a full subclass.
IIRC, init methods are allowed to re-assign self, and should therefore ALWAYS be used on the same line as alloc.
Slice *ret = [[Slice alloc] initWithBytesNoCopy:b length:len freeWhenDone:NO];
I'm not sure if that's the root cause, but it's a red-flag to me that may lead you in a good direction.
EDIT:
Actually, it has me wondering if you have overridden +alloc in your subclass and aren't returning an instance...

Too many arguments to method call

I am trying to set the initial text for what the twitter message should say in my app using a NSString from my appDelegate. Check out the code here:
NSString *tweet;
tweet=[MyWebFunction tweet:appDelegate.stadium_id];
if([deviceType hasPrefix:#"5."]){
// Create the view controller
TWTweetComposeViewController *twitter = [[TWTweetComposeViewController alloc] init];
[twitter setInitialText:#"#%",tweet];
The problem is, is there is an error at the twitter setInitialText that there are Too many arguments to method call, expected 1, have 2. ?!?!?
Any help is greatly appreciated. :)
The TWTweetComposeViewController method setInitialText only takes one argument, being of type NSString*. You cannot simply format any and all NSString variables passed to a method as you can with the NSString method stringWithFormat (which is, I imagine, where you've seen the syntax [NSString stringWithFormat:#"%#", myString]).
In your case, you either need to simply call:
[twitter setInitialText:tweet];
or call:
[twitter setInitialText:[NSString stringWithFormat:#"%#", tweet]]
EDIT
I feel it necessary to add, to further your understanding, that a method only takes a variable number of arguments (such as stringWithFormat) when its declaration ends with ...
For example, looking in the docs for NSString reveals that stringWithFormat is declared as such:
+(id) stringWithFormat:(NSString *)format, ...;
Similarly, arrayWithObjects in NSArray is declared as such:
+(id) arrayWithObjects:(id)firstObj, ...;
which one would use like:
NSString* myString1 = #"foo";
NSString* myString2 = #"bar";
NSNumber* myNumber = [NSNumber numberWithInt:42];
NSArray* myArray = [NSArray arrayWithObjects:myString1, myString2, myNumber, nil];
Try [twitter setInitialText:tweet];
If you really need formatted text for a more complex case, try
[twitter setInitialText:[NSString stringWithFormat:#"%#", tweet]];
"[twitter setInitialText:#"#%",tweet];"
you just got your "#" and your "%" the wrong way round it should be
[twitter setInitialText:#"**%#**",tweet];

Objective-C: How to perform a performSelector:#selector?

Well, I'm creating a custom SEL like:
NSArray *tableArray = [NSArray arrayWithObjects:#"aaa", #"bbb", nil];
for ( NSString *table in tableArray ){
SEL customSelector = NSSelectorFromString([NSString stringWithFormat:#"abcWith%#", table]);
[self performSelector:customSelector withObject:0];
}
I got a error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Sync aaaWithaaa]: unrecognized selector sent to instance
but if i run it with the real method name it works!
[self performSelector:#selector(aaaWithaaa:) withObject:0];
How to solve it out?
You've already created selector from string - pass it to performSelector: method:
[self performSelector:customSelector withObject:0];
Edit: Mind, that if your method takes parameter then you must use colon when create selector from it:
// Note that you may need colon here:
[NSString stringWithFormat:#"abcWith%#:", table]
NSArray *tableArray = [NSArray arrayWithObjects:#"aaa", #"bbb", nil];
for ( NSString *table in tableArray ){
SEL customSelector = NSSelectorFromString([NSString stringWithFormat:#"abcWith%#:", table]);
[self performSelector:customSelector withObject:0];
}
- (id)performSelector:(SEL)aSelector withObject:(id)anObject
First argument is SEL type.
SEL customSelector = NSSelectorFromString([NSString stringWithFormat:#"abcWith%#", table]);
[self performSelector:customSelector withObject:0];
Close.
The difference is that with #selector(aaaWithaaa:) you pass a method name but with #selector(customSelector:) you're passing a variable of type SEL (with a spare colon).
Instead, you just need:
[self performSelector:customSelector withObject:0];
The other difference is that you write your string with a colon at the end, but you stringWithFormat: has none. It's important; it means that the method takes a parameter. If your method has a parameter, it needs to be there, i.e.,
[NSString stringWithFormat:#"abcWith%#:", table]