Unrecognised selector yet debug can see it - objective-c

I have a class that contains a collection of objects. I am trying to create a method that will return the first member of the collection matching a supplied predicate.
Here is the collection method:
...
//predicate is a boolean method that accepts an object as its single parameter
-(id<Notation>) getFirstChildMatching: (SEL) predicate declaredInInstance:(id) instance
{
NSMethodSignature *sig = [[instance class] instanceMethodSignatureForSelector:predicate];
NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:sig];
[myInvocation setTarget:instance];
[myInvocation setSelector:predicate];
int numItems = childNotations.count;
for(int i=0;i< numItems;i++)
{
id<Notation> thisNotation = [childNotations objectAtIndex:i];
[myInvocation setArgument:thisNotation atIndex:2];
BOOL result =NO;
[myInvocation retainArguments];
[myInvocation invoke];
[myInvocation getReturnValue:&result];
if (result)
return thisNotation;
}
return nil;
}
I have created a test class that tests this method.
Here is the test method plus the predicate:
- (void) testGetFirstChildMatching
{
Leaf *line1 = [[Leaf alloc] initWithValue:1 step:Step_A andNumber:1];
Leaf *line2 = [[Leaf alloc] initWithValue:2 step:Step_B andNumber:2];
SEL mySelector = #selector(valueIs1:);
id<CompositeNotation> compositeNotation = [[CompositeNotation alloc] init];
[compositeNotation addNotation:line1];
[compositeNotation addNotation:line2];
id notation = [compositeNotation getFirstChildMatching: mySelector declaredInInstance:self];
STAssertEquals(YES, [notation isKindOfClass:[Leaf class]], #"Should be of type Leaf: %#", notation);
//Leaf *found = ((Leaf *)notation);
STAssertEquals([notation value], line1.value, #"Should have found line 1 with value 1: actual %i", [notation value]);
[line1 release];
[line2 release];
}
-(BOOL) valueIs1: (Leaf *) leaf
{
if (leaf.value == 1)
return YES;
return NO;
}
What I am finding is that on the "if (leaf.value == 1)" line I am getting an "unrecognized selector sent to class". What doesn't make sense is that the debugger can see the value property and it's value so the object clearly has that select.
Any ideas?
btw Leaf implements the notation protocol

Typo in function definition?
-(BOOL) valueIs1: (Leaf *) Leaf // <== should be "leaf" not "Leaf" ?
The fact that you're getting an "unrecognized selector sent to class", not instance, implies that leaf is a Class. Two things to check:
Ensure that Leaf's initializer initWithValue is returning an object.
Ensure that addNotation: is correctly adding Leafs to the array.

I eventually found the problem.
It was this line
[myInvocation setArgument:thisNotation atIndex:2];
it should have been
[myInvocation setArgument:&thisNotation atIndex:2];
Thanks

Related

Question about isKindOfClass

Please look at this code snipped
- (SoapRequest*)AddFlyData:(id)_target
action:(SEL)_action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Yeah");
} else {
NSLog(#"Not Yeah");
}
}
Why, when i look on my console, I get
2011-09-06 23:08:00.917 soap-test[2133:207] Not Yeah
I'm a beginner and I'm completely confused .. :s When I look in the Debugger, the variable type is SoapArray (who is the parent class of FlyNetArrayOfDataSet).
I used a method from SoapArray to initiate my instance of 'datasets', that means the class is automatically defined as Soap and not as FlyNetArrayOfDataSet ?!
Thank you
EDIT: I made a mistake, it's not NSArray but it inherits from SoapArray
This is the header file of the class FlyNetArrayOfDataSet
#import "Soap.h"
#interface FlyNetArrayOfDataSet : SoapArray
{
}
+ (NSMutableString*) serialize: (NSArray*) array;
#end
But that didn't explain me why isKindOfClass returns false ..
EDIT2: Ok I have the response of my question..
I used this method to initialize my instance
FlyNetArrayOfDataSet * arr = [FlyNetArrayOfDataSet arrayWithObject:data];
This is a static method of the superclass SoapArray that create an instance of SoapArray (Helper) .. but not an instance of FlyNetArrayOfDataSet (!)
Look at its implementation :
+ (id)arrayWithObjects:(id)firstObj, ...{
SoapArray* a = [SoapArray array];
id eachObject;
va_list argumentList;
if (firstObj) {
[a.items addObject: firstObj];
va_start(argumentList, firstObj);
while (eachObject = va_arg(argumentList, id)) {
[a.items addObject: eachObject];
}
va_end(argumentList);
}
return a;
}
If I initialize my instance like this
FlyNetArrayOfDataSet * arr = [[FlyNetArrayOfDataSet alloc] init];
It's work perfectly and the method isKindOfClass return true :-)
Suppose you have a class named "FlyNetArrayOfDataSet" which inherits from (=is a subclass of) NSArray.
If you instantiate a variable like:
FlyNetArrayOfDataSet *arr = [[FlyNetArrayOfDataSet alloc] init];
As you can see, I'm initializing the array with a method of NSArray. However, my "arr" object will be of kind FlyNetArrayOfDataSet, and NOT NSArray, because I called the FlyNetArrayOfDataSet class (see [FlyNetArrayOfDataSet arrayWithObject....).
NSLog(#"%d", [arr isKindOfClass:[FlyNetArrayOfDataSet class]]);
NSLog(#"%d", [arr isKindOfClass:[NSArray class]]);
Both will return "1", which means "true", because arr is an object of the FlyNetArrayOfDataSet class, which inherits from NSArray.
EDIT
Let's see if I can explain it better:
arr1 = [[FlyNetArrayOfDataSet alloc] init];
arr2 = [[NSArray alloc] init];
Both objects, arr1 and arr2, are created with the same method, which is defined in the class NSArray. However, in the first case the class which is being called is FlyNetArrayOfDataSet and in the second case is NSArray. Thus, arr1 will be an object of class FlyNetArrayOfDataSet, while arr2 will be of class NSArray.
The difference can be seen in this code:
NSLog(#"%d %d",
[arr1 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr1 isKindOfClass:[NSArray class]]
);
NSLog(#"%d %d",
[arr2 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr2 isKindOfClass:[NSArray class]]
);
The output of this code is:
1 1 ( = true true)
0 1 ( = false true)
that's because FlyNetArrayOfDataSet is a SoapArray and SoapArray is not a FlyNetArrayOfDataSet.
if datasets were an instance of SoapArray, you will see "Soap" in the following example:
- (SoapRequest*)addFlyData:(id)target
action:(SEL)action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Fly");
}
else if ([datasets isKindOfClass:[SoapArray class]]) {
NSLog(#"Soap");
}
else {
NSLog(#"???");
}
}
it's possible that an instance of SoapArray is also FlyNetArrayOfDataSet. the other possibilities are:
a SoapArray
subclass other than FlyNetArrayOfDataSet.

Why does NSInvocation getReturnValue: lose object?

I need your help. I have some problems with NSInvocation 'getReturnValue:' method. I want to create UIButton programmatically, and even more, I want to create it dynamically using NSInvocation and through passing value through the NSArray (that's why I wrapped UIButtonTypeRoundedRect).
Listing.
NSLog(#"Button 4 pushed\n");//this code executed when button pushed
Class cls = NSClassFromString(#"UIButton");//if exists {define class},else cls=nil
SEL msel = #selector(buttonWithType:);
//id pushButton5 = [cls performSelector:msel withObject:UIButtonTypeRoundedRect];//this code works correctly,but I want to do this by NSInvocation
//---------------------------
NSMethodSignature *msignatureTMP;
NSInvocation *anInvocationTMP;
msignatureTMP = [cls methodSignatureForSelector:msel];
anInvocationTMP = [NSInvocation invocationWithMethodSignature:msignatureTMP];
[anInvocationTMP setTarget:cls];
[anInvocationTMP setSelector:msel];
UIButtonType uibt_ = UIButtonTypeRoundedRect;
NSNumber *uibt = [NSNumber numberWithUnsignedInt:uibt_];
NSArray *paramsTMP;
paramsTMP= [NSArray arrayWithObjects:uibt,nil];
id currentValTMP = [paramsTMP objectAtIndex:0];//getParam from NSArray
NSInteger i=2;
void* bufferTMP;
//if kind of NSValue unwrapp it.
if ([currentValTMP isKindOfClass:[NSValue class]]) {
NSUInteger bufferSize = 0;
NSGetSizeAndAlignment([currentValTMP objCType], &bufferSize, NULL);
bufferTMP = malloc(bufferSize);
[currentValTMP getValue:bufferTMP];//copy currentVal to bufer
[anInvocationTMP setArgument:bufferTMP atIndex:i];// The +2 represents the (self) and (cmd) offsets
}else {
[anInvocationTMP setArgument:&currentValTMP atIndex:i];//Again,+2 represents (self) and (cmd) offsets
}
void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
NSLog(#"sizeof(UIButton)=%i,sizeof(result)=%i,methodreturnlength = %i,sizeof(*result)=%i",class_getInstanceSize(NSClassFromString(#"UIButton")),sizeof(result),[[cls methodSignatureForSelector:msel] methodReturnLength],sizeof(*result));
id pushButton5;
pushButton5=result;
//---------------------------
NSLog output: sizeof(UIButton)=140,sizeof(result)=4,methodreturnlength = 4,sizeof(*result)=1
The problem is that value from NSInvocation is pointer of size 4 bytes. It should point to UIButton object,size of 140 bytes. But actually refers to 1 byte data. So what does happen with UIButton object,that should be initialized by 'buttonWithType:'?
Added after getting some answers:
To clarify: I want to get UIButton object, but after this code id pushButton5 = (id) result; ,when I try to work with pushButton5,it causes EXC_BAD_ACCESS. Can someone help me?
Can this happen because of this?
Class cls = NSClassFromString(#"UIButton");
...
[anInvocationTMP setTarget:cls];
It is correct, isn't it?
If you're using ARC, I would replace this:
void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
With this:
CFTypeRef result;
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:&result];
if (result)
CFRetain(result);
UIButton *pushButton5 = (__bridge_transfer UIButton *)result;
The reason is that the invokation's return object is not retained, so it will go away, even if you immediately assign it to an object reference, unless you first retain it and then tell ARC to transfer ownership.
result has type void* and your sizeof(*result) expression is measuing sizeof(void), which apparently yields 1 in your compiler.
To check type of an Objective-C object, use isKindOfClass: method:
id resObj = (id)result;
if ([resObj isKindOfClass:[UIButton class]])
NSLog(#"mazel tov, it's a button");
Just be sure it's really an objective-c object first.
The return value is a UIButton* not a UIButton. Thus everything looks fine in your code.
It's not a problem.
First, getReturnValue: should come after the invocation. So,
[anInvocationTMP getReturnValue:result];
[anInvocationTMP invoke];
should be
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
Next, you should forget about the size of UIButton itself. What buttonWithType returns is UIButton*, not UIButton. In Objective-C, you should never directly deal with the object itself. You should always work with a pointer to the object.
Finally, (Objective-)C's sizeof operator is purely compile-time operation. So, sizeof(*result) doesn't know at all what result points to at the run time. But it doesn't matter... you shouldn't care about the size of UIButton, as I already told you.
Really answer that I needed was... How do you think where ? Yes, in documentation.
Use the NSMethodSignature method methodReturnLength to determine the size needed for buffer :
NSUInteger length = [[myInvocation methodSignature] methodReturnLength];
buffer = (void *)malloc(length);
[invocation getReturnValue:buffer];
When the return value is an object, pass a pointer to the variable (or memory) into which the object should be placed :
id anObject;
NSArray *anArray;
[invocation1 getReturnValue:&anObject];
[invocation2 getReturnValue:&anArray];
So the problem solved. Thanks for your respond guys.

Variadic function without nil termination

I am trying to create a method like the following:
- (void)setCondition:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
But since I'm not great with preprocessor, I hit an issue that I have fixed in the following code snippet, but I'd like to know if there's not cleaner way to achieve what I want which is to stop after the provided arguments
+ (CRCondition *)conditionWithFormat:(NSString *)format,... {
CRCondition *condition = [[CRCondition alloc] init];
NSArray *conditionSliced = [condition sliceFormatOperationFromString:format];
condition->_leftOperand = [[conditionSliced objectAtIndex:0] retain];
condition->_operator = [condition operatorFromString:[conditionSliced objectAtIndex:1]];
condition->_rightOperand = [[conditionSliced objectAtIndex:2] retain];
id eachObject;
va_list argumentList;
va_start(argumentList, format);
while ((eachObject = va_arg(argumentList, id))) {
if ([condition->_leftOperand isEqualToString:#"%K"]) {
[condition->_leftOperand release];
if ([eachObject isKindOfClass:[NSString class]])
condition->_leftOperand = [eachObject retain];
else
condition->_leftOperand = [[eachObject description] retain];
}
else if ([condition->_rightOperand isKindOfClass:[NSString class]] &&
[condition->_rightOperand isEqualToString:#"%#"]) {
[condition->_rightOperand release];
condition->_rightOperand = [eachObject retain];
}
else
break;
}
va_end(argumentList);
if (![condition isOperatorValid]) {
NSException *exception = [NSException exceptionWithName:#"Invalid Condition Operator"
reason:#"The operator passed is invalid. Must follow the following regex pattern: ([(=><)|(A-Z)]{1,2})"
userInfo:nil];
[exception raise];
}
return [condition autorelease];
}
The problem is with the while loop that circles and go past the provided arguments (I'm aware of why it's providing me other value, cmd args and such)
If you need any more explanation please add comments so I can get back to you.
The usual approach would be to parse the format string first and figure out how many arguments should follow it based on that (there is usually exactly one valid number of arguments for any format string). If the number of arguments is not deducible from a format string, it is common to terminate the list with nil (as in arrayWithObjects:...).

What is the easiest way to init / create / make an NSInvocation with target, selector and arguments?

What is the easiest way to make an nsinvocation with target, selector and arguments?
This page adresses several nice constructors for nsinvocation. http://www.a-coding.com/2010/10/making-nsinvocations.html, but they doesen't cover arguments.
The following code created as a category will give you a nice way to create NSInvocations easily.
.h file
#interface NSInvocation (BetterInit)
+ (id)invocationWithSelector:(SEL)selector target:(id)target arguments:(id)firstArgument, ...;
#end
.m file
#implementation NSInvocation (BetterInit)
+ (id)invocationWithSelector:(SEL)selector target:(id)target arguments:(id<NSObject>)firstObject, ... {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
[invocation setTarget:target];
[invocation setSelector:selector];
id eachObject;
va_list argumentList;
int index = 2; // should start at 2 since 0 and 1 is reserved _self and _cmd
// The first argument isn't part of the varargs list
if (firstObject) {
[invocation setArgument:&firstObject atIndex:index];
index ++;
// Start scanning for arguments after firstObject.
va_start(argumentList, firstObject);
// As many times as we can get an argument of type "id"
while(eachObject = va_arg(argumentList, id)) {
[invocation setArgument:&eachObject atIndex:index];
index ++; // Increase index
}
va_end(argumentList);
}
NSAssert(index == [[invocation methodSignature] numberOfArguments], #"you should have same number of arguments as methodsselector");
return invocation;
}
#end
This class will allow you to create an NSInvocation easily for any message, with any argument types:
http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

#selector - With Multiple Arguments?

I have been using #selector today for the first time and have not been able to work out how to do the following? How would you write the #selector if you had more than one argument?
No arguments:
-(void)printText {
NSLog(#"Fish");
}
[self performSelector:#selector(printText) withObject:nil afterDelay:0.25];
Single argument:
-(void)printText:(NSString *)myText {
NSLog(#"Text = %#", myText);
}
[self performSelector:#selector(printText:) withObject:#"Cake" afterDelay:0.25];
Two arguments:
-(void)printText:(NSString *)myText andMore:(NSString *)extraText {
NSLog(#"Text = %# and %#", myText, extraText);
}
[self performSelector:#selector(printText:andMore:) withObject:#"Cake" withObject:#"Chips"];
Multiple Arguments: (i.e. more than 2)
NSInvocation
- (id)performSelector:(SEL)aSelector
withObject:(id)anObject
withObject:(id)anotherObject
From the Documentation:
This method is the same as performSelector: except that you can supply two arguments for aSelector. aSelector should identify a method that can take two arguments of type id. For methods with other argument types and return values, use NSInvocation.
so in your case you would use:
[self performSelector:#selector(printText:andMore:)
withObject:#"Cake"
withObject:#"More Cake"]
As an alternative for NSInvocation when you have more than two parameters, you can use NSObject's -methodForSelector: as in the following example:
SEL a_selector = ...
Type1 obj1 = ...
Type2 obj2 = ...
Type3 obj3 = ...
typedef void (*MethodType)(id, SEL, Type1, Type2, Type3);
MethodType methodToCall;
methodToCall = (MethodType)[target methodForSelector:a_selector];
methodToCall(target, a_selector, obj1, obj_of_type2, obj_of_type3);
I had an issue where I needed to use the afterDelay along with multiple arguments to my #selector method. Solution? Use a wrapper function!
Say this is the function I want to pass to #selector:
-(void)myFunct:(NSString *)arg1 andArg:(NSString *)arg2 andYetAnotherArg:(NSString *)arg3;
Obviously, I can't even use withObject: withObject: here, so, make a wrapper!
-(void)myFunctWrapper:(NSArray *)myArgs {
[self myFunct:[myArgs objectAtIndex:0] andArg:[myArgs objectAtIndex:1] andYetAnotherArg:[myArgs objectAtIndex:2]];
}
and use it by doing:
NSArray *argArray = [NSArray arrayWithObjects:string1,string2,string3,nil];
[self performSelector:#selector(myFunctWrapper:) withObject:argArray afterDelay:1.0];
This way I can have multiple arguments and use the selector with delay.
#selector(printText:andMore:)
[self performSelector:#selector(printText:andMore) withObject:#"Cake" withObject:#"More Cake"];
Another option is to use an even shorter syntax:
#import <objc/message.h> // objc_msgSend
...
((void (*)(id, SEL, Type1, Type2, Type3))objc_msgSend)(target, a_selector, obj1, obj_of_type2, obj_of_type3);
Elaborating on Ben-Uri's answer, which can be written way shorter.
E.g. calling the UIView method - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view can
be done as follows:
SEL selector = #selector(covertPoint:toView:);
IMP method = [viewA methodForSelector:selector];
CGPoint pointInB = method(viewA, selector, pointInA, viewB);
As KennyTM pointed out, the selector syntax is
#selector(printText:andMore:)
You call it with
performSelector:withObject:withObject.
... if you need more arguments or different types, you need to use NSIvocation
Using NSInvocation as you specify you can create an NSObject category that implements
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;
Something like:
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setSelector: aSelector];
int index = 2; //
for (NSObject *argument in arguments) {
[invocation setArgument: &argument atIndex: index];
index ++;
}
[invocation invokeWithTarget: self];
}
from: iOS - How to implement a performSelector with multiple arguments and with afterDelay?