How can I build NSInvocation for the Class method? - objective-c

I would like to build the invocation using NSClassFromString and NSSelectorFromString for the class method and the selector.
I tried to build invocation in the following way:
NSMethodSignature *signature;
NSInvocation *inv;
Class targetClass = NSClassFromString(#"NSString");
SEL selector = NSSelectorFromString(#"stringWithString:");
id arg = #"argument";
Method method = class_getInstanceMethod(targetClass, selector);
// why is the method nil when I run the code?
struct objc_method_description* desc = method_getDescription(method);
if (desc == NULL || desc->name == NULL){
return nil;
}
signature = [NSMethodSignature signatureWithObjCTypes:desc->types];
inv = [NSInvocation invocationWithMethodSignature:signature];
[inv setSelector:selector];
[inv setArgument:&arg atIndex:2];
[inv invoke];
__autoreleasing id returnObj;
[inv getReturnValue:&returnObj]; // get created object
Since 'method' is always 'nil' this approach does not work. Why?
What is the proper way to execute class method via invocation for the above code?

Your code is too complicated. You can get an NSMethodSignature* object without messing around with the runtime.
signature = [NSString methodSignatureForSelector:#selector(stringWithString:)];

stringWithString: is a class method, your code is using class_getInstanceMethod().
Try changing class_getInstanceMethod() to class_getClassMethod().

Related

Cant stub a method using OCMock

I am using OCMock on an objC project.
I have the following code:
DB_Account *Lena = [[DB_Account alloc] init];
Lena.niceName = #"Lena";
Lena.userId = #"Lena";
id mockStorageManager = OCMClassMock([V4_StorageManager class]);
[[[mockStorageManager stub] andReturn:Lena] getAccountByDBId:#1];
id mockDBNotificationManager = OCMClassMock([DBNotificationManager class]);
id partialV4DBNotificationManagerMock = OCMPartialMock([V4_DBNotificationManager manager]);
[[[mockDBNotificationManager stub] andReturn:(NotificationPolicy *)[NotificationPolicy Never]] getNotificationPolicy];
[[[partialV4DBNotificationManagerMock stub] andReturn:mockDBNotificationManager] dbNotificationManager];
BOOL shouldShow = [[V4_DBNotificationManager manager] showOnLoginExpired:Lena];
assertThatBool(shouldShow,is(isFalse()));
this code fails to compile on the following line:
[[[mockDBNotificationManager stub] andReturn:(NotificationPolicy *)[NotificationPolicy Never]] getNotificationPolicy];
with this error:
Error:(95, 5) multiple methods named 'getNotificationPolicy' found with mismatched result, parameter type or attributes
this method returns an object of type NotificationPolicy *, no other class implements or declares a method with this name.
What is wrong?
I had to do this to solve the problem
[(DBNotificationManager*)[[mockDBNotificationManager stub]
andReturn:[NotificationPolicy Never]] getNotificationPolicy];

Can't get Selector to work for my Swift Function

I have a mixed Swift and Objective C application. The swift app uses some ObjectiveC libaries to handle OAuth2 authentication. Part of that is a callback to a delegate method once the OAuth2 request for a token has completed.
The following code is being executed in an Objective C library (GTMOAuth2) which uses a selector which I have passed in:
if (delegate_ && finishedSelector_) {
SEL sel = finishedSelector_;
NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:sel];
[invocation setTarget:delegate_];
[invocation setArgument:&self atIndex:2];
[invocation setArgument:&auth atIndex:3];
[invocation setArgument:&error atIndex:4];
[invocation invoke];
}
The function I'd like this to invoke is in my swift viewController and looks like such:
func authentication(viewController: GTMOAuth2ViewControllerTouch, finishedWithAuth: GTMOAuth2Authentication, error: NSError)
{
if (error != nil)
{
var alertView: UIAlertView = UIAlertView(title: "Authorisation Failed", message: error.description, delegate: self, cancelButtonTitle: "Dismiss")
alertView.show()
}
else
{
// Authentication Succeeded
self.mytoken = finishedWithAuth.accessToken
}
}
The selector I am currently passing in is:
let mySelector: Selector = Selector("authentication:viewController:finishedWithAuth:error:")
and is being used as a parameter in this call:
let myViewController: GTMOAuth2ViewControllerTouch = GTMOAuth2ViewControllerTouch(authentication: auth, authorizationURL: authURL, keychainItemName: nil, delegate: self, finishedSelector: mySelector)
Can anyone tell me why my function is never being called? It always seems to fail on the line where it creates an NSInvocation.
I've tried multiple selector strings and each one seems to fail. Am I missing something?
I've also tried putting "#objc" in front of the func name, to no avail.
The Swift method
func authentication(viewController: GTMOAuth2ViewControllerTouch,
finishedWithAuth: GTMOAuth2Authentication,
error: NSError)
is exposed to Objective-C as
-(void)authentication:(GTMOAuth2ViewControllerTouch *) viewController
finishedWithAuth:(GTMOAuth2Authentication *) finishedWithAuth
error:(NSError *)error
which means that the selector is
Selector("authentication:finishedWithAuth:error:")
Generally, the first parameter name is not part of the selector. The only exception
are init methods, where the first parameter name is merged into the Objective-C
method name. For example, the Swift initializer
init(foo: Int, bar: Int)
translates to Objective-C as
- (instancetype)initWithFoo:(NSInteger)foo bar:(NSInteger)bar
and the selector would be
Selector("initWithFoo:bar:")
Tested it and it is indeed like Martin R said.
let mySelector: Selector = Selector("authentication:finishedWithAuth:error:")
The first parameter for swift function is generally nameless when looked at it's selector.

How can I perform an obj-c message with a selector and variable number parameters? [duplicate]

This question already has answers here:
iOS - How to implement a performSelector with multiple arguments and with afterDelay?
(10 answers)
Calling a selector with unknown number of arguments using reflection / introspection
(4 answers)
Closed 8 years ago.
Is there any method like this:
- (id)performSelector:(SEL)selector withParameters:(NSArray *)parameters;
and I can invoke a obj-c message like this:
// invoke a message with 3 parameters
[obj performSelector:#selector(evaluate:name:animate:) withParameters:#[#1, #"test", #YES]];
// invoke a message with 1 parameter which is an array containing 3 components.
[NSArray performSelector:#selector(arrayWithArray:) withParameters:#[#[#1, #"test", #YES]]];
If there is no such method like this. How can I implement this with Obj-C runtime? Is it impossible?
Use NSInvocation
- (id)performSelector:(SEL)selector withParameters:(NSArray *)parameters
{
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:selector];
for (int i = 0; i < parameters.count; ++i)
{
id para = parameters[i];
[invocation setArgument:&para atIndex:2+i];
}
[invocation invoke];
id ret;
[invocation getReturnValue:&ret];
return ret;
}
Note: this implementation only works if the invoked method takes ObjC objects as arguments and return an object. i.e. it won't work for something takes int or return double.
If you want it works for primitive types/struct, you have to check NSMethodSignature for arguments type and convert object to that type then pass it to setArgument:atIndex:.
Read this question for more detailed ansowers

Return value for performSelector:

What will the return value for performSelector: if I pass a selector that returns a primitive type (on object), such as 'week' on NSDateComponents (which will return an int)?
An example of using NSInvocation to return a float:
SEL selector = NSSelectorFromString(#"someSelector");
if ([someInstance respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[[someInstance class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:someInstance];
[invocation invoke];
float returnValue;
[invocation getReturnValue:&returnValue];
NSLog(#"Returned %f", returnValue);
}
I think you cannot get the return value from performSelector. You should look into NSInvocation.
New answer to old question~
There is a more concise way of getting the return value from performSelector
NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(height) object:nil];
[invo start];
CGFloat f = 0;
[invo.result getValue:&f];
NSLog(#"operation: %#", #(f));
in which
- (CGFloat)height {
return 42;
}
output
2017-03-28 16:22:22.378 redpacket[46656:7361082] operation: 42
To answer the second part of the question, another way to invoke a selector that returns a primitive would be to get a function pointer and invoke it as such, as in (assuming someSelector returns a float and has no arguments);
SEL selector = NSSelectorFromString(#"someSelector");
float (*func)(id,SEL) = (float (*)(id,SEL))[someInstance methodForSelector: selector];
printf("return value is: %f", (func)(someInstance, selector));
I tried the NSInvocation implemented as suggested by dizy, its working as expected.
I also tried the another way i.e.
int result = objc_msgSend([someArray objectAtIndex:0], #selector(currentPoint));
in the above case, we are bypassing the compiler and inserting objc_msgSend call explicitly as described in the blog:
http://www.cocoawithlove.com/2011/06/big-weakness-of-objective-c-weak-typing.html
In this case, I got the following warning:
Implicitly declaring library function 'objc_msgSend' with type 'id (id, SEL, ...)'
which is obvious because we are calling library function directly.
So, I implemented with the NSInvocation which is working perfectly fine for me.

What's the purpose of the setSelector method on the NSInvocation class?

I don't understand why we have to call the setSelector method on NSInvocation objects when that information is already passed via the invocationWithMethodSignature.
To create an NSInvocation object we do the following:
SEL someSelector;
NSMethodSignature *signature;
NSInvocation *invocation;
someSelector = #selector(sayHelloWithString:);
//Here we use the selector to create the signature
signature = [SomeObject instanceMethodSignatureForSelector:someSelector];
invocation = [NSInvocation invocationWithMethodSignature:signature];
//Here, we again set the same selector
[invocation setSelector:someSelector];
[invocation setTarget:someObjectInstance];
[invocation setArgument:#"Loving C" atIndex:2];
Notice that we passed the selector to [SomeObject instanceMethodSignatureForSelector: someSelector]; and again to [invocation setSelector:someSelector];.
Is there something I'm missing?
A signature is not a selector. A selector is the name of a message. The signature defines the parameters and the return value. You can have many selectors with the same signature, and vice versa. If you look at NSMethodSignature, you will note that there is no -selector method; signatures do not carry around a particular selector.
Consider the following
- (void)setLocation:(CGFloat)aLocation;
- (void)setLocation:(MyLocation*)aLocation;
They have the same selector #selector(setLocation:), but different signatures.
- (void)setX:(CGFloat)x;
- (void)setY:(CGFloat)y;
These have the same signature, but different selectors.
Selectors from the ObjC Programming Language may be a useful reference for understanding this.
A method signature only defines the return type, and the number and type of arguments. It doesn't include anything about the selector name. For example, all of these methods have the same signature, despite having different selectors:
-(void) foo:(NSString*)fooString;
-(void) bar:(NSString*)barString;
-(void) baz:(NSString*)bazString;
This is kind of a side-answer, but the fact that you can do the following helped me understand better the separation between method signatures and selectors.
This code is within a View Controller
NSMethodSignature *sig = nil;
sig = [[self class] instanceMethodSignatureForSelector:#selector(viewDidAppear:)];
NSInvocation *myInvocation = nil;
myInvocation = [NSInvocation invocationWithMethodSignature:sig];
[myInvocation setTarget:_somePopoverController];
[myInvocation setSelector:#selector(dismissPopoverAnimated:)];
BOOL animate = YES;
[myInvocation setArgument:&animate atIndex:2];
[myInvocation invoke];
Since UIViewController's viewDidAppear:
and UIPopoverController's dismissPopoverAnimated: both take a BOOL argument and return void, you can create the method signature using one selector, but send the invocation to another.