I want to use a selector on an NSObject instance without the need for an implemented protocol. For example, there's a category method that should set an error property if the NSObject instance it's called on supports it. This is the code, and the code works as intended:
if ([self respondsToSelector:#selector(setError:)])
{
[self performSelector:#selector(setError:) withObject:[NSError errorWithDomain:#"SomeDomain" code:1 userInfo:nil]];
}
However, the compiler doesn't see any method around with the setError: signature, so it gives me a warning, for each line that contains the #selector(setError:) snippet:
Undeclared selector 'setError:'
I don't want to have to declare a protocol to get rid of this warning, because I don't want all classes that may use this to implement anything special. Just by convention I want them to have a setError: method or property.
Is this doable? How?
Another option would be to disable the warning with:
#pragma GCC diagnostic ignored "-Wundeclared-selector"
You can place this line in the .m file where the warning occurs.
Update:
It works also with LLVM like this:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
... your code here ...
#pragma clang diagnostic pop
Have a look at NSSelectorFromString.
SEL selector = NSSelectorFromString(#"setError:");
if ([self respondsToSelector:selector])
It will allow you to create a selector at runtime, instead of at compile time through the #selector keyword, and the compiler will have no chance to complain.
I think this is because for some odd reason the selector isn't registered with the runtime.
Try registering the selector via sel_registerName():
SEL setErrorSelector = sel_registerName("setError:");
if([self respondsToSelector:setErrorSelector]) {
[self performSelector:setErrorSelector withObject:[NSError errorWithDomain:#"SomeDomain" code:1 userInfo:nil]];
}
I realise I'm a bit late to this thread but for completeness, you can globally turn off this warning using the target build settings.
In section, 'Apple LLVM warnings - Objective-C', change:
Undeclared Selector - NO
If your class implements the setError: method (even by declaring dynamic the setter of the eventual error property) you might want to declare it in your interface file ( .h), or if you don't like to show it that way you could try with the PrivateMethods tricky trick:
#interface Yourclass (PrivateMethods)
- (void) yourMethod1;
- (void) yourMethod2;
#end
just before your #implementation , this should hide the warnings ;).
I got that message to go away by #include'ing the file with the method. Nothing else was used from that file.
Another way to avoid this warning is to make sure your selector method looks like this:
-(void) myMethod :(id) sender{
}
Don't forget "(id) sender" if you want to accept any sender or specify a type of a sender object if you prefer.
A really comfortable macro to put in your .pch or Common.h or wherever you want:
#define SUPPRESS_UNDECLARED_SELECTOR_LEAK_WARNING(code) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wundeclared-selector"\"") \
code; \
_Pragma("clang diagnostic pop") \
It's an edit of this question for similar issue...
You can turn it off in Xcode like in the screenshot:
You can also cast the object in question to an id first to avoid the warning:
if ([object respondsToSelector:#selector(myMethod)]) {
[(id)object myMethod];
}
While the correct answer likely lies in informing Xcode through imports or registering the selector that such a selector exists, in my case I was missing a semi-colon. Make sure before you "fix" the error that perhaps, the error is correct and your code isn't. I found the error in Apple's MVCNetworking sample, for instance.
I was able to get the warning to go away by adding thenothing method (disclosure: I didn't think of this but found it by googling on scheduledtimerwithtimeinterval)
[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
target:self
selector:#selector(donothingatall:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
HTTPLogVerbose(#"%#: BonjourThread: Aborted", THIS_FILE);
}
}
+ (void) donothingatall:(NSTimer *)timer
{
}
While I appreciate knowing how to hide the warning, fixing it is better and neither Sergio's nor Relkin's techniques worked for me, for unknown reasons.
Related
Please consider the following:
#interface Test : NSObject
+ (void)testBlock:(void(^)(NSArray<NSString*>*))aBlock;
#end
#implementation Test
+ (void)testBlock:(void (^)(NSArray<NSString *> *))aBlock
{
aBlock(#[#"Hello", #"World"]);
}
#end
and then:
[Test testBlock:^(NSArray<NSNumber*>* arr){
[arr.firstObject unsignedIntegerValue];
}];
it's a crash, but the compiler doesn't even think to warn me, whereas I would like to get an error. Is it even possible? Is there some relevant Clang error or warning I could enable in Xcode in order to make it safer?
UPDATE. I don't want to create another question with a very similar problem. This is really annoying. Take a look:
NSArray<NSString*>* strings = #[#"Hello"];
for (NSNumber* num in strings)
{
[num unsignedIntegerValue];// unrecognized selector crash!!!!
}
Not even a warning. What the ...? It's so easy to leave such a code untouched during refactoring. I would really like to make compiler warn me about cases like this one.
If I have methods like:
- (BOOL)isValidRow:(NSDictionary*)contentVersionRow
do we really have to continually check like this at the beginning of the method
if(![contentVersionRow isKindOfClass:[NSDictionary class]]) {
// Handle unusual situation - probably return NO in this case
}
to really implement proper type-safety inside Objective-C methods? Because in theory the parameter is not guaranteed to point to an NSDictionary object, is this correct?
EDIT: So answers so far seem to indicate we should not check for this, but then what is the difference between checking for this and checking for nil parameter, which I assume we should do? Or should we not check for nil either, if it's not normally expected? Both cases cover the situation of a misbehaving caller.
Just like in C you are dealing with pointers in Objective-C. So saying NSDictionary * simply means "here's a pointer to a memory address that contains an instance of NSDictionary".
Example:
#import <Foundation/Foundation.h>
#interface Test : NSObject
- (void)useDictionary:(NSDictionary *)dictionary;
#end
#implementation Test
- (void)useDictionary:(NSDictionary *)dictionary
{
NSLog(#"Keys: %#", [dictionary allKeys]);
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Test *test = [[Test alloc] init];
// 1: This works fine
[test useDictionary:#{#"key": #"value"}];
// 2: This will cause a compiler warning (or error depending on the options passed to the compiler)
[test useDictionary:#"not a dictionary"];
// 3: This will compile without any warnings
[test useDictionary:(NSDictionary *)#"not a dictionary"];
}
}
The 2nd and 3rd examples will cause the program to crash at runtime. So if you want to pass incorrect things to methods, you can. Usually Xcode will warn you if you have a type-mismatch.
Update about nil-checking: If it's an API-misuse to pass nil to your method, then throw an exception. That's what exceptions are for in Objective-C: to catch programming mistakes, not to handle expected runtime issues (like an unreachable network). If your method can just silently fail if nil is passed in or handle it in a sensible way, then do that instead. For example if you have a method addValue:(NSNumber *)number that adds the given value to a sum then it wouldn't be a big deal if someone called it with nil: Just don't add anything :)
Yes, but you shouldn’t.
Obj-C is a dynamic language, so it is up to each object to determine if it responds to a certain method. It is bad style to check the class of an object.
Instead, if you want to check that an object supports a selector you should use -respondsToSelector:, but only if you handle objects not responding to that selector.
So I am doing this to initialize my selector:
//In .h
SEL selectors[3];
//In .m
selectors[0] = #selector(rotate);
selectors[1] = #selector(discharge);
And here is the problem:
When I call this in my init method in Cocos2d like this:
[self performSelector:selectors[0]];
it works fine, but when I call this line of code in a method called moveThings which is invoked through the schedule ([self schedule:#selector(moveThings:)]) at the end of my init method in Cocos2d it gives EXC_BAD_ACCESS. What is the problem with scheduling things?
UPDATE:
I have found there is a problem with the rotate function (the function being stored in selector[0]). Here it is:
-(void)rotate:(ccTime)delta {
if (((CCSprite *)[creature objectAtIndex:0]).rotation < 360) {
((CCSprite *)[creature objectAtIndex:0]).rotation++;
}
else {
((CCSprite *)[creature objectAtIndex:0]).rotation++;
}
}
If I comment the contents of the method out it works fine when called through moveThings and init.
If I change the methods contents with:
((CCSprite *)[creature objectAtIndex:0]).rotation++;
It fails... But, again, I would like to state that all of these things do work if I call it in my init method, even call it twice in a row, but it will not work (except when I take out the contents of the rotate method) if I call it through the moveThings: method which is being invoke through the schedule method it fails.
Further update:
If I call:
((CCSprite *)[creature objectAtIndex:0]).rotation++;
In moveThings (which is being, as I've said before, invoked by the schedule:(SEL) method) it fails. Where as long as it is not invoked through a method that is the called by schedule it works.
The problem is that when you call performSelector there are only two options:
have your selector take no arguments and leave the ":" off the #selector(foo) definition.
have your selector take either one or two arguments which both must be an NSObject or subclass
it is the latter that is messing you up here I suspect.
Here are the three forms of performSelector:
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
You'll note that the withObject arguments are all of type id which is an Objective C object.
The selector you're trying to use takes a ccTime which is a float and not an Objective C object as it's param and that is why things are crashing:
-(void)rotate:(ccTime)delta;
One option is to make a wrapper method that takes a wrapped ccTime and unwraps it and calls the rotate:(ccTime) method:
- (void) wrappedRotate: (NSNumber*) inDelta
{
[self rotate: [inDelta floatValue]];
}
then use
selectors[0] = #selector(wrappedRotate:);
and then call via:
[self schedule: #selector(moveThings:)]); // cocos2d schedule
...
- (void) moveThings: (ccTime) dt
{
[self performSelector: selectors[0] withObject: [NSNumber numberWithFloat: dt]];
...
}
One reason you are getting confused is because Cocos2d is using #selector in somewhat more complicated ways (see CCScheduler.m in the CCTimer::initWithTarget:selector:interval: and CCTimer::update: method in particular).
disclaimer: code typed into SO so not checked with a compiler, but the essence of what you need should be here.
One problem for sure is that you are using a variable declared inside a .h while initializing it inside the relative .m. According to the linking I'm not sure that just one variable selectors will exist (so that different files that include .h will have different versions).
First of all I suggest you to try adding the keyword extern to have
extern SEL selectors[3];
to tell your linker that it is initialized inside the relative .m and to use just that one.
I think your problem stems from your method definition which is - (void)rotate; and not - (void)rotate:(ccTime)dt;
You should adjust your selectors likewise.
If your method does not have any arguments then do not use a colon in your selector call.
// Requires #selector(foo:)
- (void) foo:(id)sender;
// Requires #selector(foo)
- (void) foo;
i've got this method:
-(void)reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
now I want to call this method in another method like:
[self thisMethod];
But how can I do this with a method that has local declarations in it?
thank you
It doesn't matter if a method has local declarations in it, that's completely normal. Have you tried calling [self thisMethod];? Does it crash?
I'm getting this warning when I'm calling a local routine.
My code is this:
-(void)nextLetter {
// NSLog(#"%s", __FUNCTION__);
currentLetter ++;
if(currentLetter > (letters.count - 1))
{
currentLetter = 0;
}
self.fetchLetter;
}
I'm getting the warning on the self.fetchLetter statement.
That routine looks like this:
- (void)fetchLetter {
// NSLog(#"%s", __FUNCTION__);
NSString *wantedLetter = [[letters objectAtIndex: currentLetter] objectForKey: #"langLetter"];
NSString *wantedUpperCase = [[letters objectAtIndex: currentLetter] objectForKey: #"upperCase"];
.....
}
I prefer to fix warning messages, is there a better way to write this?
Thanks!
The dot notation (i.e. self.fetchLetter) is meant for properties, not for arbitrary methods. The self.fetchLetter is being interpreted as "get the 'fetchLetter' property of 'self'," which isn't what you intend.
Just use [self fetchLetter] instead.
In newer Xcode versions, even the [object method]; may trigger the warning. But sometimes we actually do need to call a property and discard the result, for example when dealing with view controllers and we need to make sure the view is actually loaded.
So we were doing:
// Ensure view is loaded and all outlets are connected.
[self view];
This now also triggers the “Property access results unused - getters should not be used for side effects” warning. The solution is to let the compiler know it's done intentionally by casting the result type to void:
(void)[self view];
You're declaring fetchLetter using syntax like this?
#property (retain) id fetchLetter;
That looks wrong for what you're doing. Properties are intended to be variable accessors that (in the case of getters) don't have any side effects.
You should declare fetchLetter as a method, like so:
- (void) fetchLetter;
and access it using:
[self fetchLetter]
I just got my problem resolved, in my case a CoreLocation Project, using both answers from Tom and Chris -
I declare:
#property (strong, nonatomic)CLLocationManager *locationManager;
And implemented like:
#synthesize locationManager = _locationManager;
....
- (void) dealloc {
[self locationManager];
}