In a simple subclass of UIViewController I just implemented a selector as a callback to refresh UI
-(void)afterfollowAction:(id)object{
//blabla
}
but finally I found respondsToSelector can not recognise it. Test code:
SEL callback = #selector(afterFollowAction:);
BOOL test1 = [self respondsToSelector:callback]; //NO
BOOL test2 = [self respondsToSelector:#selector(afterFollowAction:)]; //NO
BOOL test3 = [[self class] instanceMethodForSelector: #selector(afterFollowAction:)]; //YES
BOOL test4 = [self respondsToSelector:#selector(viewWillDisappear:)]; //YES
[self performSelector:callback withObject:nil]; //crash
test2 shows not the local variable callback's issue,
test4 shows it knows it is a UIViewController and self is not nil
test3 shows actually it knows the selector is implemented
but if you invoke it with "performSelector" it throw exception "unrecognized selector"
I searched the web and I did not make error with ":" or nil thing;
I put the declaration of the selector to the header file, but makes no use.
I restarted the Mac and iPhone but it is the same result.
This is a simple class in which I do not re-implement respondsToSelector: selector.
So, Did I make wrong use of respondsToSelector? I just want to make the callback outside this class.
Environment: Mac OS X 10.11.3, Xcode 7.2.1, iOS 9.2.1
Update: find(cmd+F) without match case hide the f/F issue. Thanks to Kiran!
It should be
SEL callback = #selector(afterfollowAction:);
BOOL test1 = [self respondsToSelector:callback]; //NO
BOOL test2 = [self respondsToSelector:#selector(afterfollowAction:)]; //NO
BOOL test3 = [[self class] instanceMethodForSelector: #selector(afterfollowAction:)]; //YES
BOOL test4 = [self respondsToSelector:#selector(viewWillDisappear:)]; //YES
[self performSelector:callback withObject:nil]; //crash
Your are using capital "F"
Related
Let's say I wanted to be able to intercept any method calls to a UIViewController subclass.
First of all, I swizzle the +(instancetype)alloc method and I check if the current instance isKindOfClass:[UIViewController class]. If it is I go ahead and instantiate my proxy with the target.
///swizzled Alloc
+ (instancetype)monitoredAlloc {
id obj = [self monitoredAlloc];
if([obj isKindOfClass:[UIViewController class]]) {
id proxy = [PMGProxy proxyWithObject:obj];
return proxy;
}
return [self monitoredAlloc];
}
---------------------------------------
/// Proxy class
#implementation PMGProxy
+ (instancetype)proxyWithObject:(id)obj {
PMGProxy *proxy = [self alloc];
proxy.obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:_obj];
[invocation invoke];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.obj methodSignatureForSelector:sel];
}
- (Class)class {
return [self.obj class];
}
The problem is that I get crashes, so I would expect the implementation of my Proxy is wrong... What am I doing wrong?
Here is the exception:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
From the error it seems that the coder is only happy to accept a different class returned from initCoder: rather than earlier in the process at the alloc stage.
Might be worth looking up NSSecureCoding for more detail on the whole process.
While you’re at it, take a look at the stack track that resulted in your exception, it will give you a bit more perspective on just how deep this rabbit hole goes.
I'm facing the following problem and I already tried a lot. I have also read the others Questions in Stackoverflow like:
Objective-C: Calling selectors with multiple arguments
and the Cocoa Core Competencies about Selectors, but I'm searching for the best way to pass a variable of arguments to a selector.
-(void) runAllStatusDelegates : (SEL)selector
{
for (NSValue *val in self.statusDelegates)
{
id<StatusDelegate> delegate = val;
if ([delegate respondsToSelector:selector])
{
[delegate performSelector:selector];
}
}
}
This method is responsible to call the methods inside the delegates. The parameter is a Selector. My Problem is that the selector can have 0 - 3 arguments, as shown below.
-(void) handleBluetoothEnabled:(BOOL)aEnabled
{
if (aEnabled)
{
[self.statusDelegate bluetoothEnabled];
if (_storedPenSerialNumber != nil && ![_storedSerialNumber isEqual:kUnknownPenID])
{
[self runAllStatusDelegates: #selector(penConnected : _storedSerialNumber : _storedFirmware:)];
}
}
else
{
[self.statusDelegate bluetoothDisabled];
}
}
-(void) handleChooseDevice:(BluetoothDeviceList*)aDevices
{
NSLog(#"Handle Choose Device");
[self runAllStatusDelegates: #selector(chooseDevice:aDevices:)];
}
-(void) handleDiscoveryStarted
{
NSLog(#"Discovery Started");
[self runAllStatusDelegates: #selector(searchingForBluetoothDevice)];
[self.statusDelegate handleStatus:#"Searching for your digipen"];
}
This implementation isn't working because the performSelector is not recognizing the selector.
I also tried to implement it with #selector(penConnected::) withObject:_storedSerialNumber but then I have to implement another method with additional arguments as well and I don't want that.
I'm new to objective-c so I'm not so familiar with all possibilities.
My idea is to pass a String and an Array of arguments to runAllStatusDelegates and build up the selector inside that method, but is this the best way or are there more convenient ways?
I am personally not a fan of NSInvocation for complex signatures. Its really great for enqueueing a simple function call on a queue and running it when you need it but for your case, you know the selector so you don't really need to go the invocation route. I typically find invocations are more useful if you don't actually know the selector you want to call at compile time, maybe its determined by your API etc.
So what I would do is simply pass a block into your runAllStatusDelegates method that will execute against all your delegates:
- (void)performSelector:(SEL)selector againstAllDelegatesWithExecutionBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
for (id<StatusDelegate> delegate in self.statusDelegates)
{
if ([delegate respondsToSelector:selector])
{
blockToExecute(delegate);
}
}
}
Then when you want to call your delegates with a function it looks like this:
[self performSelector:#selector(handleAnswerOfLifeFound)
againstAllDelegatesWithExecutionBlock:^(id<StatusDelegate> delegate){
[delegate handleAnswerOfLifeFound];
}];
I guess the only downside might be that you could change the selector and pass a different function into the block. How I would solve this is by actually making sure not all methods are optional, or if they are optional to make the actual check inside the block, this would clean up the signature:
- (void)callAllDelegatesWithBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
for (id<StatusDelegate> delegate in self.statusDelegates)
{
blockToExecute(delegate);
}
}
and then your actual usage for an optional method:
[self callAllDelegatesWithBlock^(id<StatusDelegate> delegate){
if([delegate respondsToSelector:#selector(handleAnswerOfLifeFound)]){
[delegate handleAnswerOfLifeFound];
}
}];
Still error-prone but at least a bit tidier.
You can use NSInvocation for this case
SEL theSelector = #selector(yourSelector:);
NSMethodSignature *aSignature = [NSMethodSignature instanceMethodSignatureForSelector:theSelector];
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:self];
[anInvocation setArgument:&arg1 atIndex:2];
[anInvocation setArgument:&arg2 atIndex:3];
[anInvocation setArgument:&arg3 atIndex:4];
[anInvocation setArgument:&arg4 atIndex:5];
//Add more
Note that the arguments at index 0 and 1 are reserved for target and selector.
For more info http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
you can binding the arguments to the selector
NSDictionary *argInfo=#{#"arg1":arg1,#"arg2":arg2,...};
objc_setAssociatedObject(self,#selector(chooseDevice:aDevices:),argInfo,OBJC_ASSOCIATION_COPY)
[self runAllStatusDelegates: #selector(chooseDevice:aDevices:)];
then in the
-(void) runAllStatusDelegates : (SEL)selector
{
for (NSValue *val in self.statusDelegates)
{
id<StatusDelegate> delegate = val;
if ([delegate respondsToSelector:selector])
{
NSDictionary *argInfo=objc_getAssociatedObject(self, selector);
//call the fun use arginfo
}
}
}
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.
I am working on an app and I got stuck at the point where I can't seem to retrieve the value of a BOOL set in a class.
I spent too much time already on it, been through all the questions I found that seem to cover the matter.
The bad thing here is that I get something, but not what I need (I get a 0, which means, I guess, that the value wasn't retrieved correctly as it should be 1).
The things I tried are :
pass a pointer to my first class and access to my BOOL like this:
//in some method
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
by declaring it (talking of the pointer) as a property in my second class (and importing the h. file from my first class, where my BOOL is declared as property too):
#property FirstClass *pointerFirstClass;
But I got 0 using this.
The other shot I gave was add my BOOL in the first class and create an instance of the class in my second class
//in some method
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
But I got 0 too.
As Booleans are primitive types, like in C, I get a bit confused since I am new to object-oriented programming, I don't know how I could like create a getter for this, for example.
I also tried to do a - (BOOL)getBOOLValue method in my first class, and call this method in my second class and assign it to a BOOL in that second class.
But the result wasn't better.
Am I missing something?
Is there a way to get my value that I didn't think of or didn't know about yet?
I am running low on thoughts on how to get around this, it shouldn't be that hard IMO so I hope it is something simple that I just left aside.
EDIT :
Some actual code. I am working between 2 files called AppDelegate (yes, the actual one) and WelcomeViewController (so a VC).
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
BOOL inRegion; //thought of this making my BOOL as a property of AppDelegate
}
#property (strong, nonatomic) UIWindow *window;
#property BOOL inRegion; //Declaring my BOOL here to make it accessible for another class
- (BOOL)getBOOLValue; //An attempt to pass my BOOL value
AppDelegate.m
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
self.inRegion = YES; //Set my BOOL to TRUE
}
else if (state == CLRegionStateOutside)
{
self.inRegion = NO; //Else set it to False
}
- (BOOL)getBOOLValue
{
return inRegion; //Tried to create a custome "getter"
}
WelcomeViewControler.m (I changed nothing in the .h file)
I said I tried many things, right now, this is the last version of my code.
//Simply trying to do a Segue on a condition...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
As said, I want to retrieve the BOOL value of the AppDelegate.
Thank you.
This code doesn't make sense:
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
The first line doesn't do anything. You're not assigning anything to the property, and you're not doing anything with the value. Furthermore, the second line doesn't relate to the first line in any way that we can see from the code you've provided. Try this instead:
self.pointerFirstClass = [[FirstClass alloc] init];
self.pointerFirstClass.myBOOL = YES;
NSLog(#"myBOOL = %d", self.pointerFirstClass.myBOOL);
In other words, you need to be sure that self.pointerFirstClass points to a valid object. And then you need to make sure that you've assigned the value you want to the myBOOL property of that object.
Update: This looks like a case where you're talking to the wrong object. Look at this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
This is surely not what you really want. The application object is a single object -- a real singleton, in fact, meaning that there is and can be only one application object. That object has a delegate object, and that's a specific instance of your AppDelegate class. In this code, though, you're creating a new instance of AppDelegate, one that's different from the one that the application is using. Any changes that are made to the actual application delegate in response to messages from the application will not be reflected in the new object that you've created.
What I think you want is to get the actual application delegate object, and you can do that using:
[[UIApplication sharedApplication] delegate];
So, change your code to look like this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
// note: you don't need an else clause if it doesn't do anything
}
That way, you'll be talking to the same object that the app uses, which is the one that has the inRegion property set in response to the location manager call.
UPDATE - Now we can see your code the problem is obvious, you are trying to access the appDelegate by creating a new one...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
Instead you should be doing this....
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]
--
Not sure if you are posting your actual code? but the first example you give...
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
Shouldn't the second line be
NSLog(#"%d", self.pointerFirstClass.myBOOL);
Also this property...
#property FirstClass *pointerFirstClass;
Won't retain it once you've set it, it needs to be
#property (nonatomic,strong) FirstClass *pointerFirstClass;
In the second example...
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
You allocate and initialise a new FirstClass object and then check the property straight away, if you are not setting this to YES in the init then it will be false
Like I say, maybe you're not posting your actual code?
I guess what you want is initializing myBOOL to 1.
If so, you need do something as following
#implement FirstClass
- (id)init
{
self = [super init];
if(self) {
_myBOOL = 1;
}
return self;
}
// Other methods
#end
EDIT:
The comments is why you get 0.
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init]; // this is the problem.
// you create a new appdelegate,
// and never call locationManager:didDetermineState:forRegion:
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
rewrite your code as following:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
I'm using Reachability in my iPad app and discovered some issues when using modalViewControllers.
In my mainViewController I have a BOOL variable determining weather I'm online or not. Here's my code:
// mainViewController.h
BOOL online;
// mainViewController.m
- (void)reachabilityChanged:(NSNotification *)note
{
if([[note object] isReachable]) {
online = YES;
}
else {
online = NO;
}
}
- (void)getOnline
{
NSLog(#"%d", online);
}
// modalViewController.m
#import "mainViewController.h"
- (IBAction)dismissMe
{
mainViewController *main = [[mainViewController alloc] init];
[main getOnline];
[self dismissModalViewControllerAnimated:YES];
}
When I'm calling [self getOnline] within the mainViewController, it returns 1 ('cause I am online).
But: when I'm calling [main getOnline] within the modalViewController, it returns 0 in the log.
Does anybody know why?!
I also tried to put the online variable as a #property into the modalViewController to handle the if online stuff within the modal. But when I assign a value to it (from the main), and log it within the modal, it always returns (NULL).
Hope, you can help me! With best regards, Julian
Short answer: because they use different instances of the online variable.
Long answer: you should only declare BOOL online in the header, not define it. Defining should happen in the .m file, like this:
In the mainViewController.h:
extern BOOL online; // Declare the variable
In the mainViewController.m:
BOOL online; // Define the variable
// the rest of your code
The way your code is written, a separate BOOL online is created for each .m file that includes mainViewController.h; I am sure this is not what you intended.