Following code throws an exception.
vcClass is a Class object (inheritor from UIViewController). Self contains my implementation of viewWillAppear:
SEL viewWillAppearSEL = #selector(viewWillAppear:);
IMP viewWillAppearWithSuperIMP = [self methodForSelector:viewWillAppearSEL];
class_addMethod(vcClass, viewWillAppearSEL, viewWillAppearWithSuperIMP, #encode(BOOL));
NSMethodSignature *methodSignature = [vcClass instanceMethodSignatureForSelector:viewWillAppearSEL];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:viewWillAppearSEL];
With message:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSInvocation setArgument:atIndex:]: index (1) out of bounds [-1, -1]
Additional info: iOS5, ARC.
Can someone explain me what's wrong?
UPDATED:
This code code gives me responds message. So my class object is correct
[vcClass instancesRespondToSelector:viewWillAppearSEL] ? NSLog(#"responds") : NSLog(#"not responds");
Alse Im getting crash right after [invocation setSelector:viewWillAppearSEL];. That's why I called topic title as Unexpected exception with NSInvocation.
UPDATED2:
Also my implementation of viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
Class parentViewController = [self superclass];
void (*superViewWillAppear)(id, SEL, BOOL) =(void(*)(id, SEL, BOOL))class_getMethodImplementation(parentViewController, _cmd);
superViewWillAppear(self, _cmd, animated);
NSLog(#"view will appear with super");
}
One problem with your code is the type encoding that you are passing to class_addMethod(). This type encoding must include: 1) the return type, 2) the types for self and _cmd (the first two hidden parameters), and then 3) the types of all the other parameters.
For a method like - (void)viewWillAppear:(BOOL)animated, the type encoding should be the string
v#:c
v -- for void, return type
# -- for id, type for self
: -- for SEL, type for _cmd
c -- for char, this is what BOOL is. This is what you get when you did #encode(BOOL)
BOOL *arg1;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:viewWillAppearSEL];
[invocation setArgument:&arg1 atIndex:2]; // argument indexing is offset by 2 hidden args
Related
I created an objective-c method which will invoke a method via NSInvocation:
typedef void (^ScriptingEmptyBlock)();
typedef void (^ScriptingErrorBlock)(NSError *error);
- (void)scripting_execute:(NSString *)operation withParams:(nullable NSArray *)args {
SEL selector = [self scripting_selectorForOperation:operation];
Class class = [self class];
NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:self];
for (int idx = 0; idx < args.count; idx ++) {
id arg = args[idx];
[invocation setArgument:&arg atIndex:idx + 2];
}
ScriptingEmptyBlock success = args[1];
// Breakpoint added on next line to test for nil
success(); // this is nil and would crash!
// (lldb) po args.count
// 3
// (lldb) po success
// Printing description of success:
// (ScriptingEmptyBlock) success = 0x0000000000000000
// (lldb) po args[1]
// (Function)
//[invocation getArgument:&success atIndex:2]; // also tried this and got nil as well
[invocation invoke];
}
The method takes an "operation" which is translated into a selector by overriding scripting_selectorForOperation: in subclasses and then performs the invocation.
All of that works, except when the invoked method has block arguments they are nil, I added the test for nil I describe with comments, when attempting to read the closure from the array it will be nil.
Called like:
let successClosure: ScriptingEmptyBlock = {
print("Renamed product")
}
let errorClosure: ScriptingErrorBlock = { error in
print("Failed to rename product: \(error)")
}
let params:[Any] = [ "testName", successClosure, errorClosure]
object.scripting_execute (ScriptOperation.updateProductName.rawValue, withParams: params)
Why is closure becoming nil?
success is not nil (in fact, NSArray cannot contain nils). If you print it like NSLog(#"%#", success);, it will say (Function), not (null). And if you print its class like NSLog(#"%#", [success class]);, it will say _SwiftValue. Basically, it is a Swift value that is bridged into Objective-C.
The problem is that the object success points to is not an Objective-C block. It is a Swift closure, and Swift closures are not the same as Objective-C blocks. Trying to use invoke it as if it were an Objective-C block causes undefined behavior. po in the debugger prints it wrong probably because it is printing it assuming it were type ScriptingEmptyBlock (a block type). If you do po (id) success, it will print (Function).
As to how you can explicitly put an Objective-C block into the array from Swift, the only way I figured out to do it something like:
let params:[Any] = [ "testName",
successClosure as (#convention(block) () -> Void)!,
errorClosure as (#convention(block) (NSError) -> Void)!]
object.scripting_execute (ScriptOperation.updateProductName.rawValue,
withParams: params)
I am not sure why it's necessary to put the function type inside a !, but it doesn't seem to work otherwise. Maybe someone else can find a better way.
I must admit that I don't fully understand why this is happening, but as far as I can tell this has nothing to do with using NSInvocation and would happen even if we just passed a Swift closure to an Objective-C function via a parameter of type id. Passing an Objective-C block via id works just fine, not sure why: Swift closures are supposed to be compatible with Objective-C blocks. As you know, elements of NSArray are of type id, so any Objective-C object can be an array element.
To work around this problem of accessing a Swift closure passed via id in Objective-C one can introduce a wrapper class.
// In a header:
#interface EmptyBlockWrapper : NSObject
#property EmptyBlock _blk;
#end
// In an implementation file (just an empty implementation):
#implementation EmptyBlockWrapper
#end
Then we can use a wrapper instance instead of a block as an array element in Swift:
let myBlock : EmptyBlock = {
print("In Swift EmptyBlock...")
}
let myBlockWrapper = EmptyBlockWrapper()
myBlockWrapper._blk = myBlock
In an Objective-C method we can call it as follows, assuming args is NSArray *:
EmptyBlockWrapper * emptyBlockWrapper = args[1];
emptyBlockWrapper._blk();
Hopefully this is helpful. Of course, this is just a simplified example to give you an idea; this could be made much fancier.
I'm getting a EXC_BAD_ACCESS exception when I call performSelector:withObject: from a object that does implement the method I'm trying to call. Here's my code
SEL newSelector = NSSelectorFromString(#"mySelector:withCustomObject:");
[self performSelector:newSelector withObject:myCustomObject];
This causes a crash. However when I do this
[self performSelector:#selector(mySelector:withCustomObject:) withObject:myCustomObject];
it works.
Any ideas on why this is happening?
PS: none of the parameters are nil.
MORE CODE:
// My code to call this method
SEL newSelector = NSSelectorFromString(#"mySelector:withCustomObject:");
[self performSelector:newSelector withObject:self withObject:myCustomObject];
// this code is NOT called.
- (void) mySelector:(jObject *)sender withCustomObject:(jEvent *)customObject
{
NSDictionary *handlerData = [aProperty objectAtIndex:[event positionInMethodStack]];
NSString *newTitle = [handlerData objectForKey:#"newTitle"];
}
"mySelector:withCustomObject:" is the signature of a method with 2 arguments, such as
- (void)mySelector:(id)firstArgument withCustomArgument:(id)secondArgument { ... }
But you call performSelector:withObject:, which sends a message with only one argument to mySelector. The second argument is undefined, which probably causes the crash.
So if mySelector actually has 2 arguments, use performSelector:withObject:withObject:, otherwise fix the signature of the selector.
Hello
I have a problem with the following code in a GTMTestCase:
- (void)testSomething {
myType *year = [myType valueFromString:#"1978"];
STAssertTrue([year isKindOfClass:[XBNumberAttribute class]], #"Must be subtype.");
}
If I build this (=execute the tests), I get a "segmentation fault "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents, Command /bin/sh failed with exit code 139 " error. This however goes away as soon as I retain the year object (which is actually autoreleased in the valueFromString method, see below):
- (void)testSomething {
myType *year = [[myType valueFromString:#"1978"] retain];
//STAssertTrue(([year retainCount] == 2), #"Retain count wrong");
STAssertTrue([year isKindOfClass:[XBNumberAttribute class]], #"Must be subtype.");
}
Uncommenting the retainCount assertion indeed shows that the retain count is 2 at this point. However, if I put [year release] at the end of the method, the build fails again with the same error as explained above.
What is the matter here?
For the sake of completeness I include the code of valueFromString:
+ (id)valueFromString:(NSString *)pString {
return [[[myType alloc] initWithString:pString] autorelease];
}
And here the initWithString method:
- (id)initWithString:(NSString *)pString {
if (self = [super initWithString:pString]){
}
return self;
}
pointing to the following super type method:
- (id)initWithString:(NSString *)pString
{
if (self = [super init]) {
theNumber = [NSNumber numberWithInt:[pString intValue]];
}
return self;
}
After switching on all the autorelease debug options (see http://www.cocoabuilder.com/archive/cocoa/87251-how-do-debug-an-autorelease-crash.html), I found the solution:
The variable theNumber is not accessed via the property accessor, so it is not actually retained (in the initWithString of the super type method, last code snippet). As it is however receiving a release in this type's dealloc method because it is marked as a #property (retain) (information not included here, but important!), I got an error when the autorelease of the child object was called (first code snippet): it released the supertype and with it the attribute which was not there anymore.
I'm using NSInvocation as follows:
In my init, I'm writing this in my viewDidLoad:
SEL mySelector;
mySelector = #selector(initParsersetId:type:);
NSMethodSignature * sig = nil;
sig = [[self class] instanceMethodSignatureForSelector:mySelector];
myInvocation = nil;
myInvocation = [NSInvocation invocationWithMethodSignature:sig];
[myInvocation setTarget:self];
[myInvocation setSelector:mySelector];
And I'm calling it like this:
Idea *tempIdea = [[Idea alloc]init];
tempIdea = [genericArray objectAtIndex:indexPath.row];
idea.ideaId = tempIdea.ideaId;
[tempIdea release];
NSNumber *_id_ = [NSNumber numberWithInt:idea.ideaId];
[myInvocation setArgument:_id_ atIndex:2]; //CRASHING AT THIS LINE
My application is crashing at the indicated line. Can anybody please help me?
It is not very clear from your codes; however, I see something suspicious. Hopefully, it may provide your some helpful hints.
First, I don't see you retain the instance (auto released from [NSInvocation...). Since the instance from [NSInvocation...] is auto-released, your class level variable myInvocation would not retain it after viewDidLoad event.
Second thing in your codes is that the selector is a kind of customized constructor, beginning with init..... I am not sure if you can invoke the event within the same instance. Another point is that if your init... method to be invoked returns self? It should be.
You may output some messages in your selector event by using NSLog function. All the messages by NSLog will be in your XCode's output console.
I've found the answer but I'm not convinced how. Actually, initially I was writing all the initialisation code in viewDidLoad and simply reusing the NSInvocation object by passing it the different argument since NSInvocation is a mutable object. It didn't work. Then I wrote a method with all the initialisation code inside it and called that method every time I used the NSInvocation object and it worked...
You need to give setArgument: the address of the argument you are passing, and not the argument itself:
[myInvocation setArgument:&_id_ atIndex:2];
NOT
[myInvocation setArgument:_id_ atIndex:2];
Also, are you sure your function takes an NSNumber as first argument?
I want to invoke a selector of a method that has the usual NSError** argument:
-(int) getItemsSince:(NSDate *)when dataSelector:(SEL)getDataSelector error:(NSError**)outError {
NSArray *data = nil;
if([service respondsToSelector:getDataSelector]) {
data = [service performSelector:getDataSelector withObject:when withObject:outError];
// etc.
... which the compiler doesn't like:
warning: passing argument 3 of 'performSelector:withObject:withObject:' from incompatible pointer type
Is there any way around this short of encapsulating the pointer in an object?
Check out NSInvocation, which lets you "performSelector" in a much more flexible way.
Here's some code to get you started:
if ([service respondsToSelector:getDataSelector]) {
NSArray *data;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[service methodSignatureForSelector:getDataSelector]];
[invocation setTarget:delegate];
[invocation setSelector:getDataSelector];
// Note: Indexes 0 and 1 correspond to the implicit arguments self and _cmd,
// which are set using setTarget and setSelector.
[invocation setArgument:when atIndex:2];
[invocation setArgument:outError atIndex:3];
[invocation invoke];
[invocation getReturnValue:&data];
}
NSError** is not an object (id), which performSelector wants for each of the withObject args. You could go to NSInvocation, but that seems like a lot of work if this is just a single message you want to use. Try defining an intermediate selector method that takes as an arg your wrapped NSError** in an object, and let that method do the performSelector on it (which I think was probably what you meant at the end of your question?)
I'm not positive, but you may want to take a look at using an NSInvocation instead of -performSelector:withObject:withObject. Since NSInvocation takes arguments of type void*, it may/should let you set whatever you want as an argument.
It'll require several more lines of code than a simple performSelector: call, but it'll probably be more convenient than wrapping your pointer in an object.