iOS Define SEL from another class - ios

I'm trying to have another object call a selector. I'm attempting to define this selector from another class by defining the selector property. It doesn't seem to be working like I expect.
ComboBox.h
#property (nonatomic) SEL onComboSelect;
ComboBox.m
-(void)doneClicked:(id) sender
{
[textField resignFirstResponder]; //hides the pickerView
NSLog(#"DONE CLICKED CALLED");
[self performSelector:#selector(onComboSelect)];
}
OtherClass.h
#interface OtherClass : BaseViewController
{
ComboBox *combo;
}
-(void)comboSelector;
OtherClass.m
// in viewDidLoad
combo = [[ComboBox alloc] init];
combo.onComboSelect = #selector(comboSelector);
-(void)comboSelector
{
NSLog(#"COMBO SELECTOR");
}
I see "DONE CLICK CALLED" in the logs, but not "COMBO SELECTOR". So I know doneClicked is being called, but the selector doesn't seem to be working. What am I doing wrong? Is there a better way to do this?

A #selector is just a method name - it does not include any context about the class on which it is defined. So this [self performSelector:#selector(onComboSelect)] is just invoking the method on self. In addition to the selector, you also need a reference to the object on which you want to call it.
Notice how some built-in classes (like UIControl) take both a target object and action selector.

There are a 2 major issues in your code.
1.
onComboSelect is a SEL so no need to use the #selector again.
Instead of:
[self performSelector:#selector(onComboSelect)];
Use :
[self performSelector:onComboSelect];
2.
You are calling the selector on self from ComboBox class, so it'll call the selector on ComboBox object (if defined) not on OtherClass object

Your answers were helpful. Here is what I did:
added to ComboBox.h
#property (nonatomic, weak) UIViewController *parentViewController;
added to ComboBox.m
-(void)doneClicked:(id) sender
{
[textField resignFirstResponder]; //hides the pickerView
if ([parentViewController respondsToSelector:#selector(comboSelector)])
[parentViewController performSelector:#selector(comboSelector)];
}
added to OtherClass.m
combo.parentViewController = self;
#property (nonatomic) SEL onComboSelect <-- This property is not needed in ComboBox.h.

SEL is a point of objc_seletor,and in object_seletor runtime can find the
objc_method ,that define
objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
and IMP is the point of method,and you can find this in "runtime.h" file.
Any noe kow how to find objc_method by object_seletor ? and I cannot find the define of objc_seletor struct.

Related

What happens when a parametrized selector is called with no parameters?

I was going over this example in which a selector is used. I have copied the code from there for convenience.
// MYTapGestureRecognizer.h
#interface MYTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) NSString *data;
#end
// MYTapGestureRecognizer.m
#implementation MYTapGestureRecognizer
#end
// =====================
....
MYTapGestureRecognizer *singleTap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected:)];
singleTap.data = #"Hello";
.....
// ====================
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
My question is
1-When self calls the selector what parameter does it pass in the above case ?
2- Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Suppose the signature of MyTest is
- (void) MyTest : (NSString*) a;
Now constructing and calling a selector
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
I checked the following but I could not find this information
Apple docs
Rys Tutorials
Answers to your questions:-
When self calls the selector what parameter does it pass in the above case ?
If a tap is detected and the selector is called, the parameter will be an object of UITapGestureRecognizer. This will be the same instance on which the tap gesture is detected.
Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Why do you want to call the method like that, is there any special purpose?. If not, you can call the method just like
[self tapDetected:nil];
or
[self performSelector:#selector(tapDetected:) withObject:nil];
If you call the method as provided in the question, most probably it will crash.
If you wish to call the method on self, pass nil parameter to it. But i do not understand what purpose is it serving you.
Also if you do not send parameters to your methods, it is going to fail at your builds. You have to pass either the parameter or nil.
Also if your method does not accept nil parameters it might cause an exception - 'NSInvalidArgumentException'
Normally if you want to access that selector via self, use it like :
[self tapDetected:nil];
You need to handle this case in your selector, like :
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
if (tapRecognizer)
{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
else
{
//Do your work
}
}
Also not only this, if you are not sure of parameter you are passing change your selector decalartion as id, like :
-(void)tapDetected:(id)sender {
NSString *className = NSStringFromClass([id class]);
NSLog(#"Object passed is of class : %#", className);
//And make check here
if ([id isKindOfClass:[MYTapGestureRecognizer class]])
{
//Do your work here
}
}
There are no default cases, you need to handle every case manually or else app will crash.
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
It will be undefined junk. You have no guarantees about what it might contain. Most likely, it will be an invalid pointer. If you're unlucky, it might be a valid pointer to some arbitrary object and operating on it will corrupt your app's state. If you're lucky, it will crash so you can find the problem.

NULL Custom Class Objects when outside viewDidLoad?

u1Option is an Option (custom class) object which is
declared and called in my ViewController viewDidLoad as:
- (void)viewDidLoad {
[super viewDidLoad];
Option *u1Option = [[Option alloc]init];
[u1Option setName: #"test"];
NSLog(#"Test1 Result: %#", u1Option.name);
}
Option is a custom class inheriting from NSObject and has a property declared in Option.h:
#property NSString *name;
However, if try to use u1Option in a IBAction, nothing is passed and while I get the "test" string in the Test1 NSLOG, on the contrary I get NULL in the Test2 NSLOG.
- (IBAction)addFirstOption:(UIButton *)sender {
NSLog(#"Test2 Result: %#", u1Option.name);
}
The Option instance is destroyed as soon as viewDidLoad returns. You need to put it in a property or instance variable. If it's already a property or instance variable then you are re-defining it, so use:
u1Option = [[Option alloc] init];
And this issue can be avoided in future by using self.u1Option or _u1Option.

accessing a property of an instantiated object with my custom init method

Here's my class with my custom init method:
// Piece.h
#import <Foundation/Foundation.h>
#interface Piece : CCSprite
#property (nonatomic) int pieceNumber;
+(Piece *)initWithPieceImage:(UIImage*)piece pieceName:(int)pName;
#end
// Piece.m
#import "Piece.h"
#implementation Piece
#synthesize pieceNumber = _pieceNumber;
+(id)initWithPieceImage:(UIImage *)piece pieceName:(int)pName
{
return [[[self alloc] initWithPieceImage:piece pieceName:pName] autorelease];
}
-(Piece*)initWithPieceImage:(UIImage *)piece pieceName:(int)pName
{
CCSprite *bgImage = nil;
if ( (self=[super init]) )
{
bgImage = [CCSprite spriteWithCGImage:piece.CGImage
key: [NSString stringWithFormat:#"%i",pName]];
}
return (Piece*)bgImage;
}
#end
I instantiated the Piece class like this to add it to the layer:
Piece *newPiece = [Piece initWithPieceImage:myUIImage pieceName:1];
[newPiece setPieceNumber:2]; //Error in this line
[self addChild: newPiece z:1];
However I have tried it like this and it perfectly works:
Piece *newPiece = [[Piece alloc] init];
[newPiece setPieceNumber:2];
but this is not what I want.
and here is the error I get:
[CCSprite setPieceNumber:]: unrecognized selector sent to instance 0x85f1050
Terminating app due to uncaught exception NSInvalidArgumentException, reason: -[CCSprite setPieceNumber:]: unrecognized selector sent to instance 0x85f1050
Aparently it looks like the problem is how Im trying to init my object.
I'm a newcomer to objective-c so I cant figure out what is wrong here.
any idea of what am I doing wrong here?
How can I achieve this approach and access the properties of my instantiated object with custom init method?
You have a mess in your code. In -(Piece*)initWithPieceImage:(UIImage *)piece pieceName:(int)pName you return a CCSprite object instead of a Piece. You assign self with an object but return another, of an incorrect type.
init returns the correct type (because you haven't reimplemented it), so it works, but you haven't actually initialized the image correctly.
You need to change your method like so:
-(Piece*)initWithPieceImage:(UIImage *)piece pieceName:(int)pName
{
return [super initWithCGImage:piece.CGImage key:[NSString stringWithFormat:#"%i",pName]];
}
It is because in your init method, you are for some reason creating a CCSprite object and returning that instead of the Piece object. Because of that, it will not be an object of your Piece class and will not respond to any of Piece's methods.
Instead of creating a new CCSprite object to set those properties to, you want to set those on self or super.

Find UIGestureRecognizer action (selector) name and target

I'm trying to find which action is triggered by a UIGestureRecognizer on which target. Unfortunately there is no property on a UIGestureRecognizer such as gesture.action or gesture.target. The gesture I'm analyzing is part of UIKit private implementation.
Partial Answer here
stackOverFlow Question 20066315
Here's a code snippet that will list all target/action pairs associated with a gesture recognizer:
Ivar targetsIvar = class_getInstanceVariable([UIGestureRecognizer class], "_targets");
id targetActionPairs = object_getIvar(gesture, targetsIvar);
Class targetActionPairClass = NSClassFromString(#"UIGestureRecognizerTarget");
Ivar targetIvar = class_getInstanceVariable(targetActionPairClass, "_target");
Ivar actionIvar = class_getInstanceVariable(targetActionPairClass, "_action");
for (id targetActionPair in targetActionPairs)
{
id target = object_getIvar(targetActionPair, targetIvar);
SEL action = (__bridge void *)object_getIvar(targetActionPair, actionIvar);
NSLog(#"target=%#; action=%#", target, NSStringFromSelector(action));
}
Note that you'll have to import <objc/runtime.h>, and that this uses private ivars and a class, so it could get you banned from the App Store.
I have a different solution to this which has worked for me. This is more of a design change... you cannot access the target from the captured gesture. So instead keep a reference to the object when the touch down happened and before the pan began.
#property (nonatomic, strong) UIButton *myTouchedButton; // reference to button
(void)init
{
...
[card.button addTarget:self action:#selector(cardTouchDownInside:) forControlEvents:UIControlEventTouchDown];
...
}
-(void)cardTouchDownInside:(id)sender
{
NSLog(#"touch down on object");
self.myTouchedButton = (UIButton*)sender;
}

Add additional behavior to method without subclassing

I need to add additional behavior to methods I need to extend, i.e. implement method that looks like
- (void)extendMethod:(SEL)selector forClass:(Class)class withCompletionBlock:(void (^)(void))completionBlock;
So every time Class instance call a method with SEL selector in addition should be invoked my completion block.
I've tried method swizzling, but ran into some problems: I want original method implementation to be called.
What I need is very similar with subclassing, but this should be implemented without subclassing.
UPDATE:
For example I have subclass of UIViewController named MyViewController. MyViewController have - (void)viewDidLoad method. Somewhere in application I call method
[methodExtender extendMethod:#selector(viewDidLoad)
forClass:[MyViewController class]
withCompletionBlock:^{
NSLog(#"view did load called");
}];
So after viewDidLoad method of every instance of MyViewController my completion block invoked.
I'm not sure how you want to use selector, but you can try extend any class(even ones that you don't have implementation file) by using mechanism in Objective-C know as "Categories".
From Xcode click on File->New->File (command+n)
From Cocoa Touch choose Objective-C category
Type name of your category and choose class on which you want to make category (I choosed UIButton)
Then next and Create.
Xcode will create two files for example: UIButton+extendMethod.h
Declare your method in header file and implement it in *.m file.
Using
If you want to use in let's say your View Controller in *.h file import
#import "UIButton+extendMethod.h"
and then you can call your method like this:
UIButton *button = [[UIButton alloc] init];
[button extendMethod:#selector(yourMethod:)];
Swizzling does allow you to call the original implementation, though it is just a bit confusing. Because the implementations are swapped after swizzling, you call the original implementation using the selector of the swizzled method:
- (void)mySwizzledImplementation {
// do stuff
// now call original implementation (using swizzled selector!)
[self mySwizzledImplementation];
// do more stuff
}
See also http://cocoadev.com/wiki/MethodSwizzling
There is no way (anymore)to simulate inheritance without subclassing. There use to be Posing, method swizzling is all that is left (not as elegant as posing though). Here is one way to do method swizzling correctly while being able to call the original implementation.
int swizzle_instance_methods(Class class, SEL selector, IMP replacement, IMP *store) {
#synchronized(class) {
Method method = class_getInstanceMethod(class, selector);
IMP original_imp = NULL;
if (method != NULL) {
const char *type = method_getTypeEncoding(method);
IMP original_imp = class_replaceMethod(class, selector, replacement, type);
if (original_imp == NULL)
original_imp = method_getImplementation(method);
if (original_imp != NULL && store != NULL) {
*store = original_imp;
}
}
return (original_imp != NULL);
}
}
+ (void) load
{
static IMP originalMethodImpl = NULL;
IMP customMethodImpl = imp_implementationWithBlock(^(id self_) {
NSString *descr = ((NSString(*)(id,SEL))originalMethodImpl)(self_, #selector(description);
return [NSString stringWithFormat:#"<--- %# --->",descr];
});
swizzle_instance_methods([self class], #selector(description), customMethodImpl, &originalMethodImpl);
}
I might add that this is nice for debugging and I think that it can be greate for building excellent frameworks. Alas, Apple seems to think differently and using method swizzling can result in your app being excluded from the App store. If you are not aiming for the app store then all the power to you.
It is be possible with ObjC categories. For ex, you can extend hasPrefix method of NSString as follows,
-(BOOL)hasPrefixx:(NSString *)aString
{
NSLog(#"Checking has prefix");
return [self hasPrefix:aString];
}
If you import the category, you should be able to call this method. But his means you got change the method in your call.
By the way, Method swizzling should work. Bit of explanation here.

Resources