Suppressing "'…' is deprecated" when using respondsToSelector - objective-c

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.

I found an example in the Clang Compiler User's Manual that lets 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
}

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.

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.

You could just cast fileManager to an id — ids 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.

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.

Related

Objective-C ARC and longjmp

What is the best practice for mixing Objective-C ARC with longjmp?
I am using Lua as scripting language, and my platform exports custom library for scripts. Entry points do check arguments with luaL_checkinteger(L, 2) (among others), which, in turn, may call luaL_typerror(L, 2, ...), that is implemented in Lua with setjmp/longjmp. As far as I know, ARC simply auto-generates retain/release code, but what happens if it longjmps out of scope? Will this code leak on mistyped arguments?
static int
set_tag(lua_State *L)
{
NSControl *control = (__bridge NSControl *)lua_topointer(L, 1);
[control setTag:(NSInteger)luaL_checkinteger(L, 2)]; // may longjmp!
return 0;
}
In the snippet above, control will be temporarily retained by ARC, but with longjmps uncatchable nature, corresponding release call may never happen. On the other hand, all arguments may be checked before assigning to control variable.
static int
set_tag(lua_State *L)
{
NSInteger tag = luaL_checkinteger(L, 2); // may longjmp!
NSControl *control = (__bridge NSControl *)lua_topointer(L, 1);
[control setTag:tag];
return 0;
}
Does it resolve [potential] leak above? Are there better ways to do this?
UPDATE: longjmp only unwinds to Lua internals, and never crosses any system code, except for Lua source (which is aware), and my entry points (which I hope are aware).
I'm pretty sure that second snippet does right, but I need kind of formal proof.
LATE UPDATE:
LuaJIT implements dwarf2-compatible errors, so they are just like C++ exceptions. Pass -fobjc-arc-exceptions compiler flag to arc-enabled sources with Lua code and any retained object will be released on any lua_error. Nothing to worry about now! You are still not allowed to throw errors across Cocoa runtime, though.
I recall that original Lua may be compiled with exceptions too, but I'm not sure.
Doesn't really matter if ARC is in use or not; any setjmp/longjmp that jumps over any frame of code from the system frameworks will yield undefined behavior (for the same reason that exceptions cannot be used for recoverable error handling).
So, yes, that code might leak. Might because it depends on whether the compiler emits a retain/release in that block and where. Might also because whether the compiler emits retain/release will be impacted by the optimization level and, over time, the version of the compiler.
longjmp only unwinds to Lua internals, and never crosses any system
code, except for Lua source (which is aware), and my entry points
(which I hope are aware).
That is helpful. As long as you structure your entry points such that they never intermingle system scope with Lua jumpable scopes, you should be OK. I would recommend turning off ARC in the source files where you have to manage this (and, of course, put the ObjC->Lua interface into a nicely encapsulated bit of implementation so the rest of your code can be ARC clean).
Consider, though, that there is non-obvious risk:
for(id x in anArray) {
... lua call that causes longjmp ...
}
The above would cause lua to "jump over" system code. Same goes for enumerateWithBlock:, KVO, Notification handlers, etc...
You're going to have to think very very carefully about every potential stack trigger by a call from Lua into your code. If that call triggers any kind of automated behavior on the part of the system that could then call Lua API that could trigger a longjmp, all bets are off.
longjmp() may cause crashes or leaks in ARC. Arranging the code so longjmp() and ARC don't interfere is difficult.
If the longjmp() is only for a fatal error path and you expect to halt the process in response then you may be able to ignore the problem. This is what ARC does with C++/ObjC exceptions by default. ARC code is expected to leak when exceptions are thrown. There's a compiler option to enable the clean up code for exceptions, but that hurts performance.
If the longjmp() is not a process-killing error then your best option is to turn off ARC in any code that may be skipped by a longjmp() call.

Objective-C: Instance variable used while 'self' not set... but it is

I've been tasked with cleaning up some Clang errors in a code base. I am very new to iPhone development and Objective C, but have found most of the problems trivial... this one is stumping me though, when I'm sure its something embarrassing.
From a ZAttributedString class:
- (id)initWithAttributedString:(ZAttributedString *)attr {
NSParameterAssert(attr != nil);
if ((self = [super init])) {
_buffer = [attr->_buffer mutableCopy];
_attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
}
return self;
}
The clang warning is "Instance variable used while 'self' is not set to the result of '[super or self] init...]', with the dereferencing of attr's _buffer attribute being highlighted.
If it helps, the warning also seems to mention that the problem is found when calling from this method:
- (id)copyWithZone(NSZone *)zone {
return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
}
Can anyone please explain to me what exactly the defect is here?
TIA!
Do not use -> to access instance variables, especially when the ivar is from some other object.
Do this:
_buffer = [[attr string] mutableCopy];
Same goes for that nasty attr->_attributes. Apparently, ZAttributedStringexposesattributes` as a property in the private header.
That compiler warning does seem, at the very most optimistic, entirely misleading and, likely, quite wrong in description. Filing a bug to have that clarified would be useful.
Note that #maddy's claim that using -> to access the instance variables directly in the attr string passed as it acts like a copy constructor is incorrect.
The incoming attr may be a ZAttributedString instance or an instance of a subclass or, really, an instance of any class that implements the same interface as ZAttributedString. Thus, you really must go through the accessors to guarantee that you are grabbing the correct state.
Now, as an implementation detail, ZAttributedString could require that the inbound instance be a non-subclassed instance of ZAttributedString, but it should use isMemberOfClass: to assert that requirement (and, please, don't do that).
The only spot where direct ivar access is sometimes used to pull state from another object is in the implementation of copyWithZone:, but that is exceedingly fragile and oft leads to whacky broken behavior. In fact, copyWithZone: (outside of the various plist compatible value classes) has been rife with fragility and the source of many many many bugs.
It seems like you are seeing the exact same bug as this: "[Bug 15092] New: static analyzer false positive: reports instance variable used while 'self' is not set to the result of [(super or self)] init". It has a very similar code attached to reproduce the bug.
If you run that code in Xcode 4.6.3 you can verify that it gives the same false warning as you are seeing.
The bug was resolved with the comment:
This is fixed in trunk, or at least mostly fixed -- there are still a few edge
cases where the warning will fire, but not your project.
(Dave, for now all of the main analyzer engineers do work at Apple, so there's
no real need to file duplicates. The LLVM people who don't work at Apple don't
have access to Apple Clang, which ships with Xcode, and this fix didn't make
Xcode 4.6. You can also get newer checker builds from
http://clang-analyzer.llvm.org)
As you can see the bug is fixed but still present in Xcode 4.6. Hold out for the next version of Xcode and the analyzer warning should be gone.

Did the Target-Action design pattern became bad practice under ARC?

For years I've been following a great pattern called Target-Action which goes like this:
An object calls a specified selector on a specified target object when the time comes to call. This is very useful in lots of different cases where you need a simple callback to an arbitrary method.
Here's an example:
- (void)itemLoaded {
[specifiedReceiver performSelector:specifiedSelector];
}
Under ARC it now turns out that doing something like this all of a sudden became dangerous.
Xcode throws a warning that goes like this:
PerformSelector may cause a leak because its selector is unknown
Of course the selector is unknown since as part of the Target-Action design pattern you can specify whatever selector you want in order to get a call when something interesting happens.
What bugs me most about this warning is that it says there can be a potential memory leak. From my understanding ARC doesn't bend the memory management rules but instead simply automates the insertion of retain/release/autorelease messages at the right locations.
Another thing to note here: -performSelector: does have an id return value. ARC analyzes method signatures to figure out through application of naming conventions if the method returns a +1 retain count object or not. In this case ARC doesn't know if the selector is a -newFooBar factory or simply calling an unsuspicious worker method (which is almost always the case with Target-Action anyways). Actually ARC should have recognized that I don't expect a return value, and therefore forget about any potential +1 retain counted return value. Looking at it from that point of view I can see where ARC is coming from, but still there is too much uncertainty about what this really means in practice.
Does that now mean under ARC something can go wrong which would never happen without ARC? I don't see how this could produce a memory leak. Can someone give examples of situations in which this is dangerous to do, and how exactly a leak is created in that case?
I really googled the hell out of the internet but didn't find any site explaining why.
The problem with performSelector is that ARC doesn't know what the selector which will performed, does. Consider the following:
id anotherObject1 = [someObject performSelector:#selector(copy)];
id anotherObject2 = [someObject performSelector:#selector(giveMeAnotherNonRetainedObject)];
Now, how can ARC know that the first returns an object with a retain count of 1 but the second returns an object which is autoreleased? (I'm just defining a method called giveMeAnotherNonRetainedObject here which returns something autoreleased). If it didn't add in any releases then anotherObject1 would leak here.
Obviously in my example the selectors to be performed are actually known, but imagine that they were chosen at run time. ARC really could not do its job of putting in the right number of retains or releases here because it simply doesn't know what the selector is going to do. You're right that ARC is not bending any rules and it's just adding in the correct memory management calls for you, but that's precisely the thing it can't do here.
You're right that the fact you're ignoring the return value means that it's going to be OK, but in general ARC is just being picky and warning. But I guess that's why it's a warning and not an error.
Edit:
If you're really sure your code is ok, you could just hide the warning like so:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[specifiedReceiver performSelector:specifiedSelector];
#pragma clang diagnostic pop
The warning should read like this:
PerformSelector may cause a leak because its selector is unknown. ARC doesn't know if the returned id has a +1 retain count or not, and therefore can't properly manage the memory of the returned object.
Unfortunately, it's just the first sentence.
Now the solution:
If you receive a return value from a -performSelector method, you can't do anything about the warning in code, except ignoring it.
NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor];
Your best bet is this:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor];
#pragma clang diagnostic pop
Same goes for the case in my initial question, where I completely ignore the return value. ARC should be intelligent enough to see that I don't care about the returned id, and therefore the anonymous selector is almost guaranteed not to be a factory, convenience constructor or whatsoever. Unfortunately ARC is not, so the same rule applies. Ignore the warning.
It can also be done for the whole project by setting the -Wno-arc-performSelector-leaks compiler flag under "Other Warning Flags" in project build settings.
Alternatively, you can surpress the warning on a per-file basis when you add that flag under Your Target > "Build Phases" > "Compile Sources" on the right-hand side next to the desired file.
All three solutions are very messy IMHO so I hope someone comes up with a better one.
As described above you get that warning because the compiler does not know where (or if) to put the retain/release of the performSelector: return value.
But note that if you use [someObject performSelector:#selector(selectorName)] it will not generate warnings (at least in Xcode 4.5 with llvm 4.1) because the exact selector is easy to be determined (you set it explicitly) and that's why compiler is able to put the retain/releases in the correct place.
That's why you will get warning only if you pass the selector using SEL pointer because in that case the compiler is unable to determine in all case what to do. So using the following
SEL s = nil;
if(condition1) SEL = #selector(sel1)
else SEL = #selector(sel2)
[self performSelector:s];
will generate warning. But refactoring it to be:
if(condition1) [self performSelector:#selector(sel1)]
else [self performSelector:#selector(sel2)]
will not generate any warnings
ARC is throwing the warning because it can't guarantee that the selector isn't creating an object it doesn't know about. You could theoretically receive something from that method that ARC can't handle:
id objectA = [someObject performSelector:#selector(createObjectA)];
Maybe someday it can, but right now it can't. (Note if it does know the object (it's not an id) it doesn't throw this warning).
If you're trying to simply execute a method without receiving an object back from it, I recommend using objc_msgSend. But you've gotta include in your class:
#include <objc/message.h>
objc_msgSend(someObject, action);

Can you show compiler warnings for error:nil?

Is there a compiler setting that can warn about these? Currently I'm reviewing code and putting in #warning don't use error:nil whenever I see them.
(I know it's sometimes appropriate to do, but maybe there is a better way to have the compiler check sloppy error handling?)
No, since error:nil is completely legal and totally legit (as you even say), there is no way for the compiler to check it.

How to conditionally use a new Cocoa API

In 10.6 Apple added +[NSPropertyListSerialization dataWithPropertyList:format:options:error:] and labeled the older +[NSPropertyListSerialization dataFromPropertyList:format:errorDescription:] as obsolete and soon to be deprecated. One way to use the newer call on 10.6 and above, and still run on earlier OS releases, would be something like this:
if ([NSPropertyListSerialization respondsToSelector:#selector(dataWithPropertyList:format:options:error:)]) {
data = [NSPropertyListSerialization dataWithPropertyList:dict
format:NSPropertyListXMLFormat_v1_0
options:0
error:&err];
} else {
data = [NSPropertyListSerialization dataFromPropertyList:dict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
}
Built against the 10.4 SDK (for compatibility with that release), this results in: warning: 'NSPropertyListSerialization' may not respond to '+dataWithPropertyList:format:options:error:' And, worse, since the compiler does not know about this selector, it may pass the arguments incorrectly.
Is NSInvocation the approved/best way to call new APIs that, as far as the SDK is concerned, don't yet exist?
IIRC, you want to use the 10.6 SDK and set your deployment target (MACOSX_DEPLOYMENT_TARGET) to 10.4 so the 10.5/10.6 symbols are weak-linked. Then you can use the respondsToSelector: stuff and not get warnings.
Make sure you're checking that the object can respond to the selector, of course, or you will crash on 10.4/10.5.
One other way of doing things is to declare the missing method yourself as a category of the class in question. This will get the compiler to stop complaining about not finding the method, though of course you'll still need the runtime check you're already doing to avoid actually calling the method. You might also want to wrap such a declaration using availability macros, so that it will be ignored once you do move up to using the 10.5/10.6 SDK and you won't get a different compiler complaint down the line. That would look something like this:
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 //ignore when compiling with the 10.5 SDK or higher
#interface NSPropertyListSerialization(MissingMethods)
+ (NSData *)dataWithPropertyList:(id)plist format:(NSPropertyListFormat)format options:(NSPropertyListWriteOptions)opt error:(NSError **)error;
#end
#endif