views:

698

answers:

6

I'm supporting 10.4+ by picking the most-current API at runtime:

if ([fileManager respondsToSelector:@selector(removeItemAtPath:error:)])
    [fileManager removeItemAtPath:downloadDir error:NULL];
else
    [fileManager removeFileAtPath:downloadDir handler:nil];

In this case, 10.5 and up will use removeItemAtPath:error: and 10.4 will use removeFileAtPath:handler:. Great, but I still get compiler warnings for the old methods:

warning: 'removeFileAtPath:handler:' is deprecated [-Wdeprecated-declarations]

Is there a syntax of if([… respondsToSelector:@selector(…)]){ … } else { … } that hints the compiler (Clang) to not warn on that line?

If not, is there a way to tag that line to be ignored for -Wdeprecated-declarations?


After seeing some of the answers, let me clarify that confusing the compiler into not knowing what I'm doing is not a valid solution.

+2  A: 

I found an example in the Clang Compiler User's Manual that should let me ignore the warning:

if ([fileManager respondsToSelector:@selector(removeItemAtPath:error:)])
    [fileManager removeItemAtPath:downloadDir error:NULL];
else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    [fileManager removeFileAtPath:downloadDir handler:nil];
#pragma clang diagnostic pop

It's not ideal, but will work if Clang isn't smart enough to handle this case.


Edit: …but the version of Clang shipping with Xcode doesn't seem to support it:

warning: pragma diagnostic expected 'error', 'warning', 'ignored', or 'fatal' [-Wunknown-pragmas]
Sidnicious
Can you just wrap the call in `#pragma clang diagnostic ignored "-Wdeprecated-declarations"` and then `#pragma clang diagnostic warning "-Wdeprecated-declarations"` if Clang on OS X doesn't support `push`?
Wevah
+7  A: 

You could declare a separate file that is designated for calling deprecated methods and set the per-file compiler flags in Xcode to ignore -Wdeprecated-declarations. You can then define a dummy function in that file to call the deprecated methods, and thereby avoid the warnings in your real source files.

kperryua
Good idea, especially since it gets all the deprecated API calls in one place. I moved the compatibility code into a separate file, adding `-compatibility*` methods to categories on the classes in question (for example, `-[NSFileManager compatibleRemoveItemAtPath:]`).
Sidnicious
A: 

You can use __MAC_OS_X_VERSION_MIN_REQUIRED (defined in Availability.h) in place of respondsToSelector:

#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
    // code only compiled when targeting Mac OS X and not iPhone
    // note use of 1050 instead of __MAC_10_5
    #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1050
        // code in here might run on pre-Leopard OS
    #else
        // code here can assume Leopard or later
    #endif
#endif

or:

#ifdef __MAC_10_5
  // code for Leopard or later
#else
  // code for pre-Leopard
#endif
Martin Gordon
But this only has an effect at compile time, right? The Leopard-or-later code will never even be compiled unless I change my application to target 10.5.
Sidnicious
This also does nothing to suppress the warnings.
Sidnicious
Right, you would have to have two build targets.
Martin Gordon
+4  A: 

I'm not sure if clang is smart enough to catch this, but if it's not, you could try using performSelector:withObject:withObject: or building and invoking an NSInvocation object.

Martin Gordon
performSelector: and kin are the correct solution to calling Objective-C methods at runtime when you're not certain whether they exist.
cdespinosa
+5  A: 

You could just cast fileManager to an idids are able to refer to any Objective-C object, so the compiler isn't supposed to check methods which are called on one:

[(id)fileManager removeItemAtPath:downloadDir error:NULL];

shouldn't raise any warnings or errors.

Of course, this raises other problems — namely, you lose all compile-time checking for methods called on the id. So if you misspell you method name, etc, it wont be caught until that line of code is executed.

Matt Ball
+2  A: 

If you consider any form of "confusing" the compiler to be an invalid solution, you're probably going to have to live with the warning. (In my book, if you asking how to get rid of a warning, it's unwise to look a gift horse in the mouth and say something is invalid just because it doesn't look like you'd expect.)

The answers that work at runtime involve masking the operation that's happening with dynamic dispatch so the compiler doesn't complain about the deprecated call. If you don't like that approach, you can turn off "Warn About Deprecated Functions" in your Xcode project or target settings, but that's generally a bad idea. You want to know about deprecated APIs, but in this case you want to use it without warning. There are easy and hard ways to do this, and odds are you'd consider all of them "invalid" in some form, but that doesn't prevent them from being effective, even correct. ;-)

One possible way to avoid the warnings yet still select at runtime is to use objc_msgSend() directly:

objc_msgSend(fileManager, @selector(removeFileAtPath:error:), downloadDir, nil];

This is what the Objective-C runtime does under the covers anyway, and should accomplish the result you want with a minimum of fuss. You can even leave the original line commented above it for clarity. I know the documentation says, "The compiler generates calls to the messaging function. You should never call it directly in the code you write." You alone have to decide when it's okay to bend the rules.

Quinn Taylor
Code should say what it means. If it doesn't, then the API, the language, or the programmer is broken. If the compiler is warning when the programmer is confident that his code is correct, then either the compiler needs to learn about that situation and ignore it automatically, or it needs to support a syntax that lets the it ignore that particular warning on that line (like the syntax I posted that doesn't work in the Clang that ships with Xcode). Anything else is a hack.
Sidnicious
I applaud your idealism, but sometimes you have to compromise. The code I've posted, and that others have as well, *does* say what it means. I believe you mean that the compiler should be able to divine your intent based purely on code, without jumping through hoops. That's nice and all, but how do you propose the compiler should know that you'll only call a given method on runtime where it's not deprecated? Without additional semantic information, it really can't. Syntax that can communicate such meaning would be fantastic, but until it exists, calling everything else a hack is unproductive.
Quinn Taylor
Sometimes writing code that isn't crystal clear to the casual observer is unavoidable. This is a perfect time to add a clarifying comment if necessary. Using either `performSelector:withObject:withObject:` or `objc_msgSend()` are minimally disruptive approaches that still communicate your meaning and avoid the warning. Java has annotations to suppress warnings, but C does not. Bummer, but life goes on. I'm not defending the current state of things as how it ought to be, just suggesting that if you're going to actually use the language, it's more effective to embrace its quirks than be elitist.
Quinn Taylor
To answer your question: the compiler already has a list of deprecated methods, why not give it a list of alternative methods as well? Then, if it sees a deprecated call, it could look to see if it is inside the else branch of a conditional that tests `[<OBJECT> respondsToSelector:@selector(<ALTERNATIVE>))]`.
Sidnicious
I'm only calling these solutions hacks because they have effects beyond suppressing the warning. They may affect how the compiler checks the code (it will no longer verify that the object responds to that selector or, in the case of casting to `id`, even verify that you're talking to an object) or how the code compiles (calling `performSelector:withObject:withObject:` is functionally different from calling the method directly). If it's impossible to annotate a line be ignored for a particular warning, that's a problem with the compiler. It should be brought up and corrected, not embraced.
Sidnicious
I understand your viewpoint, yet I think these answers are about as good as you can expect for now. The deprecated/alternative method approach is an interesting idea, but one that would require fundamental changes to the syntax and semantics of marking code as deprecated. I prefer the safety of compiler checking whenever possible, and avoid casting as `id` just to avoid warnings. However, spurious warnings tend to distract me more than an occasional hack, particularly when I have unit tests in place to verify that the code work correctly. :-) FWIW, I agree about per-line warning suppression.
Quinn Taylor