вівторок, 20 червня 2017 р.

Suppress deprecation warning. C / C++ / Objective-C compiler is awesome

Apple engineers just love deprecating stuff.
Code which uses deprecated functionality may very well stop working in the next three to five years and/or you would have troubles compiling it.

In mosts cases Apple gives clear direction where to look for a replacement and usually new functionality is much better: more usable, more consistent. So what about developers? Well - just deal with it! Because that's what we do!

Imagine the situation: your app support Mac OS X 10.8 and higher and is using the CFURLCreateStringByAddingPercentEscapes function. This function is marked as deprecated from OS X 10.10. Eventually Apple will remove this from it's framework and you will not be able to compile the application unless you fix it. If you compile your App against 10.10 SDK version - you would see the deprecation warning.

To address similar issues, using Objective-C selectors we can check at runtime if an instances of an object responds to a specific selector signature (knows about this new method) and execute either new code (replacement) or old one. This works fine and actually is a recommended way. However the compiler warning stays. The code below is a typical solution.

if ([string respondsToSelector:@selector(stringByAddingPercentEncodingWithAllowedCharacters:)]) { //stringByAddingPercentEncodingWithAllowedCharacters is a new function - a "replacement"
   res = [string respondsToSelector:@selector(stringByAddingPercentEncodingWithAllowedCharacters:)];
{ //we are running under older Mac OS X version and new functionality is not available - let's use old one
   res = CFURLCreateStringByAddingPercentEscapes(..., ... );

Since the statements under ELSE block still to be compiled, we will see a warning that the CFURLCreateStringByAddingPercentEscapes is deprecated. We have worked the problem out and introduced the solution, we don't really want this warning anymore.
Great, since Objective-C compiler is C-based (and actually is the same) we can disable deprecated warnings with the compiler flag. But this will remove all deprecation warning! That's not a real Jedi path.

Googling around has shown that what people do is they are tricking compiler into hiding the deprecated call under the performSelector method. Since all of the object methods calls in Objective-C are actually just a message delivered to the object (runtime binding and invocation), you could do something like that:

[string performSelector:@selector(stringByAddingPercentEncodingWithAllowedCharacters) withObject:...]; 

(this is a hypothetical example, since we can't do the same with C-style functions)

The whole code readability thing effectively is much worse now, but come on - this is the deprecated code, which will be erased from the code in a few years anyway and is under ELSE statements with explanation in comments. From the compiler perspective this is a complete equivalent of the syntax above with only one exception: compiler is not smart enough (yet?) to display deprecation warnings for these situations.

In the initial example, deprecated function is actually a function (not a selector) and therefore I am unable to use the performSelector call to execute it. Neither I want to - this is a "dirty" hack for people who have no idea what pre-processor is.

Getting serious
By using the clang diagnostic ignored \"-Wdeprecated-declarations\" preprocessor directive we can suppress deprecation warnings for current class (file).
A more civilised way would be to suppress deprecation warning before the statement with deprecated code and allow deprecation warnings after that.
Using the code below, we can remove the warning only where we do not need it.

{ //we are running under older Mac OS X version and new functionality is not available - let's use old one
   _Pragma("clang diagnostic push")             \
   _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
     res = CFURLCreateStringByAddingPercentEscapes(..., ... );
   _Pragma("clang diagnostic pop")

Firstly, I tell preprocessor to save it's current diagnostic state. Next line, I modify it by switching off deprecation warnings. After the deprecated statement, I restore preprocessor diagnostic state - all back to normal.

Well - this is much better! Really a more civilised way.
But this effectively means that each deprecated line of code becomes three lines. This is ugly. I knew, it is not what a REAL developer would do.

So I put on my developers hat, clear dust from it and come up with a macros allowing for the following syntax

{ //we are running under older Mac OS X version and new functionality is not available - let's use old one
     res = CFURLCreateStringByAddingPercentEscapes(..., ... );

Much better, isn't it? As a bonus, we now can silence warnings for specific OS SDK versions! We also make it very clear in the code when this functionality was first deprecated.

Consider the preprocessor macroses definitions below

#define DEPRECATION_SILENCE1_true                    \
 _Pragma("clang diagnostic push")                    \
 _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")

#define DEPRECATION_SILENCE2_true  _Pragma("clang diagnostic pop")

#define WRAP_DEPRECATION_CONDITIONAL(b_const, expr)  \
do {                                                 \
  DEPRECATION_SILENCE1_##b_const                     \
  expr;                                              \
  DEPRECATION_SILENCE2_##b_const                     \
} while(0)


I am not a developer for quite a long time. Analytics and BI is honestly more awesome and inspiring. But I do enjoy writing code as a hobby. The macroses above required some concentration, attention to details, creativity and different approach to a problem - but so much satisfaction at the end!

We live in a wonderful time!

Немає коментарів: