28

On more than one occasion I've seen crashing bugs appear on iOS 3.x due to use of a new call that was introduced in 4.x without proper checking.

Is there a way for Xcode to warn about classes, methods and procedures that are only available a later version than the deployment target?

That way I could easily list through all the code and make sure it's properly conditionalized.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ben S
  • 68,394
  • 30
  • 171
  • 212

9 Answers9

27

I've actually released something which helps with testing this sort of thing. It's part of my MJGFoundation set of class called MJGAvailability.h.

The way I've been using it is to apply it in my PCH file like this:

#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED __IPHONE_4_0
#import "MJGAvailability.h"

// The rest of your prefix header as normal
#import <UIKit/UIKit.h>

Then it'll warn (with perhaps a strange deprecation warning) about APIs which are being used that are too new for the target you set as the "soft max" as per the #define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED. Also if you don't define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED then it defaults to your deployment target.

I find it useful because I can then double check which APIs I'm using that are too new for the deployment target that I've set.

mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
  • I tried this, but it gave the same errors as I posted in the comments to Ben S's answer. – nevan king Feb 09 '12 at 15:27
  • @nevanking how strange. It works for me no problem. It even means that the methods are crossed out when viewed in code completion in Xcode which is also quite handy! – mattjgalloway Feb 10 '12 at 08:49
  • @nevanking I had the same issue, but it was because I was just grabbing some code from matt's header, and forgot this important line: #define __AVAILABILITY_TOO_NEW __attribute__((deprecated("TOO NEW!"))) – Sandy Feb 13 '12 at 20:40
  • 2
    This answer works for me, while the accepted answer does not in Xcode 4.5 – borrrden Sep 28 '12 at 03:47
  • Like borrrden, take care that file might be updated when a new iOS version come – ıɾuǝʞ Dec 04 '12 at 13:39
  • I will be updating the file as soon as new versions of iOS come out. But yes, you need to pull in the update yourself as well! – mattjgalloway Dec 04 '12 at 14:22
  • @mattjgalloway Thanks for the solution. It works great. Unfortunately, it seems to omit some methods. I set `__IPHONE_OS_VERSION_SOFT_MAX_REQUIRED` to `__IPHONE_4_3` and don't get warning on `[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]` available from 5.0. Also `dispatch_queue_set_specific` is not processed. Any thoughts on that? – Michał Zygar Feb 04 '13 at 14:04
  • @MichałZygar Hmm. It works for me. Where are you settings `__IPHONE_OS_VERSION_SOFT_MAX_REQUIRED`? In general you don't really want to set that. You just want to set your minimum deployment target and let the macros do their business. – mattjgalloway Feb 04 '13 at 16:46
  • Ok, after removing `__IPHONE_OS_VERSION_SOFT_MAX_REQUIRED` it works fine now. Thanks! – Michał Zygar Feb 05 '13 at 07:46
  • Hmm this seems to have stopped working for me recently for SDKS >4.0. Any reason why? – Bo A Jul 07 '13 at 12:46
  • In the header file CFAvailability.h there seems to be a switch statement checking for compilers with attribute_availability_with_message support. This trick does not seem to work for compilers that do. I tried for example to get a warning by using the iOS 7 method [UIViewController setNeedsStatusBarAppearanceUpdate] with iOS deployment target set to iOS 6.0 but this doesn't seem to work under XCode 5.0. Any suggestions? – Werner Altewischer Oct 31 '13 at 10:13
  • I figured out a work around for the issue described in my comment above. See my response below:(http://stackoverflow.com/a/19704587/480467) – Werner Altewischer Oct 31 '13 at 11:00
  • 2
    @BenC.R.Leggiero: See [new answer below](http://stackoverflow.com/a/37128517/102008) about using "Other Warning Flags" of `-Wpartial-availability` instead – user102008 Sep 22 '16 at 20:22
  • @mattjgalloway - Your header is not working for Xcode 8 and iOS 9.3. – Duck May 08 '17 at 11:23
19

If you use XCode7.3 and above, you can set other warning flag: -Wpartial-availability, then xcode will show warning for API newer than deployment target version enter image description here

galway
  • 269
  • 2
  • 6
16

On OS X at least, with recent clang/SDK, there is now a -Wpartial-availability option (add it e.g. in "other warning options") One can then define the following macros to encapsulate code that handles runtime testing if the method is supported

#define START_IGNORE_PARTIAL _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpartial-availability\"")
#define END_IGNORE_PARTIAL _Pragma("clang diagnostic pop")

I haven't test on iOS though.

Daniel
  • 231
  • 2
  • 5
  • It works in Xcode 7. However, the warnings in referenced projects do not show up. I have to change the target to them one by one in order to see the warnings. – keithyip Jul 29 '16 at 15:46
  • This should be the accepted answer now, as Clang can natively emit the warnings you need. And if you have "treat warnings as errors" enabled (you should!) then the build won't succeed. No need to redefine CF and NS macros. – Matthew May 26 '17 at 10:09
  • this is the perfect answer. – tutu_magi Jun 20 '17 at 10:56
13

After digging through AvailabilityInternal.h, I realized that all available versions above the Deployment target are tagged with the __AVAILABILITY_INTERNAL_WEAK_IMPORT macro.

Therefore, I can generate warnings by redefining that macro:

#import <Availability.h>
#undef  __AVAILABILITY_INTERNAL_WEAK_IMPORT
#define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
    __attribute__((weak_import,deprecated("API newer than Deployment Target.")))

By placing this code in a project's precompiled header, any use of an API that might cause a crash on the lowest supported iOS version now generates a warning. If you correctly guard the call, you can disable the warning specifically for that call (modified exmaple from Apple's SDK Compatibility Guide):

#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    if ([UIPrintInteractionController class]) {
        // Create an instance of the class and use it.
    }
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
    else {
        // Alternate code path to follow when the
        // class is not available.
    }
Sverrisson
  • 17,970
  • 5
  • 66
  • 62
Ben S
  • 68,394
  • 30
  • 171
  • 212
  • I tried putting your code into my `.pch` file, but it instantly created a ton of errors in the Apple frameworks when I built. Mostly "Wrong number of arguments specified" errors. Xcode 4.2 building for iOS 3.1.3. – nevan king Feb 09 '12 at 15:20
  • 1
    This works great on LLVM-GCC __so long as you change the `#define` line to this:__ `#define __AVAILABILITY_INTERNAL_WEAK_IMPORT \ __attribute__((weak_import,deprecated)))`. [GCC doesn't take an argument](http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Type-Attributes.html) to its `deprecated` attribute, and so this answer's code will cause the build to fail. – Phil Calvin Jun 01 '12 at 12:09
  • 1
    clang added a more graceful 'available' attribute - the easiest thing I found was to hack around it (see my answer below) – xtravar Aug 05 '16 at 17:02
4

This is based on Ben S's answer, but incorporates support for GCC and LLVM-GCC. GCC's deprecated attribute doesn't take a message argument like clang's, so passing one produces a compiler error in basically every file.

Place the following code at the top of your ProjectName-Prefix.pch file to get a warning for every use of an API that may not be available in all your targeted versions:

#import <Availability.h>
#undef  __AVAILABILITY_INTERNAL_WEAK_IMPORT
#ifdef __clang__
#define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
__attribute__((weak_import,deprecated("API newer than Deployment Target.")))
#else
#define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
__attribute__((weak_import,deprecated))
#endif

As Ben says, if you're intentionally doing this (perhaps by checking for the selector at runtime), you can hide the warning using this construct:

#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    - (void)conditionallyUseSomeAPI {
        // Check for and use the appropriate API for this iOS version
    }
#pragma GCC diagnostic warning "-Wdeprecated-declarations"

Regrettably, you can't do this inside a function, at least in i686-apple-darwin10-llvm-gcc-4.2 (GCC) 4.2.1.

Community
  • 1
  • 1
Phil Calvin
  • 5,077
  • 2
  • 41
  • 35
4

To get this to work under XCode 5 you need to also redefine the NS_AVAILABLE and NS_DEPRECATED macros because CFAvailability.h distinguishes between compilers that support the attribute_availability_with_message feature. Copy the following above the "MJGAvailability.h" import in your precompiled header to get this to work with the new Apple LLVM compiler:

#import <Availability.h>
#import <Foundation/NSObjCRuntime.h>

#undef CF_AVAILABLE
#undef CF_AVAILABLE_MAC
#undef CF_AVAILABLE_IOS
#undef CF_DEPRECATED
#undef CF_DEPRECATED_MAC
#undef CF_DEPRECATED_IOS
#undef CF_ENUM_AVAILABLE
#undef CF_ENUM_AVAILABLE_MAC
#undef CF_ENUM_AVAILABLE_IOS
#undef CF_ENUM_DEPRECATED
#undef CF_ENUM_DEPRECATED_MAC
#undef CF_ENUM_DEPRECATED_IOS

#undef NS_AVAILABLE
#undef NS_AVAILABLE_MAC
#undef NS_AVAILABLE_IOS
#undef NS_DEPRECATED
#undef NS_DEPRECATED_MAC
#undef NS_DEPRECATED_IOS
#undef NS_ENUM_AVAILABLE
#undef NS_ENUM_AVAILABLE_MAC
#undef NS_ENUM_AVAILABLE_IOS
#undef NS_ENUM_DEPRECATED
#undef NS_ENUM_DEPRECATED_MAC
#undef NS_ENUM_DEPRECATED_IOS
#undef NS_AVAILABLE_IPHONE
#undef NS_DEPRECATED_IPHONE

#undef NS_CLASS_AVAILABLE
#undef NS_CLASS_DEPRECATED
#undef NS_CLASS_AVAILABLE_IOS
#undef NS_CLASS_AVAILABLE_MAC
#undef NS_CLASS_DEPRECATED_MAC
#undef NS_CLASS_DEPRECATED_IOS

//CF macros redefinition
#define CF_AVAILABLE(_mac, _ios) __OSX_AVAILABLE_STARTING(__MAC_##_mac, __IPHONE_##_ios)
#define CF_AVAILABLE_MAC(_mac) __OSX_AVAILABLE_STARTING(__MAC_##_mac, __IPHONE_NA)
#define CF_AVAILABLE_IOS(_ios) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_##_ios)

#define CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_##_macIntro, __MAC_##_macDep, __IPHONE_##_iosIntro, __IPHONE_##_iosDep)
#define CF_DEPRECATED_MAC(_macIntro, _macDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_##_macIntro, __MAC_##_macDep, __IPHONE_NA, __IPHONE_NA)
#define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA, __MAC_NA, __IPHONE_##_iosIntro, __IPHONE_##_iosDep)

#define CF_ENUM_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
#define CF_ENUM_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
#define CF_ENUM_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)

#define CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

//NS macros redefinition
#define NS_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
#define NS_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
#define NS_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)

#define NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

#define NS_ENUM_AVAILABLE(_mac, _ios) CF_ENUM_AVAILABLE(_mac, _ios)
#define NS_ENUM_AVAILABLE_MAC(_mac) CF_ENUM_AVAILABLE_MAC(_mac)
#define NS_ENUM_AVAILABLE_IOS(_ios) CF_ENUM_AVAILABLE_IOS(_ios)

#define NS_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

#define NS_AVAILABLE_IPHONE(_ios) CF_AVAILABLE_IOS(_ios)
#define NS_DEPRECATED_IPHONE(_iosIntro, _iosDep) CF_DEPRECATED_IOS(_iosIntro, _iosDep)

#define NS_CLASS_AVAILABLE(_mac, _ios) __attribute__((visibility("default"))) NS_AVAILABLE(_mac, _ios)
#define NS_CLASS_DEPRECATED(_mac, _macDep, _ios, _iosDep, ...) __attribute__((visibility("default"))) NS_DEPRECATED(_mac, _macDep, _ios, _iosDep, __VA_ARGS__)

#define NS_CLASS_AVAILABLE_IOS(_ios) NS_CLASS_AVAILABLE(NA, _ios)
#define NS_CLASS_AVAILABLE_MAC(_mac) NS_CLASS_AVAILABLE(_mac, NA)
#define NS_CLASS_DEPRECATED_MAC(_macIntro, _macDep, ...) NS_CLASS_DEPRECATED(_macIntro, _macDep, NA, NA, __VA_ARGS__)
#define NS_CLASS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) NS_CLASS_DEPRECATED(NA, NA, _iosIntro, _iosDep, __VA_ARGS__)
Werner Altewischer
  • 10,080
  • 4
  • 53
  • 60
  • 2
    Hmm, I'm still not seeing the build warnings after following your instructions on Xcode 5.0.2. – Mike Dec 03 '13 at 05:53
  • MJGAvailability.h is definitely a good starting point for this. However, with newer Xcode/SDK (Xcode 7 at least), it will not work if modules are enabled (because prefix header will not change any definition in builtin header with modules) – Daniel Apr 28 '16 at 16:27
0

Latest Xcode didn't work with other answers. This works for me (only looking for UIKit issues).

The reason is that the newer clang versions have a built-in availability attribute.

#define TESTING_COMPILATION_TARGET
// only enable when trying to diagnose what APIs are being inappropriately used
#ifdef TESTING_COMPILATION_TARGET
#import <Availability.h>

#define __MYUNSUPPORTED __attribute((deprecated("API version unsupported")))

#define __MYUNSUPPORTED_IOS_NA __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_2_0
#define __MYUNSUPPORTED_IOS_2_1
#define __MYUNSUPPORTED_IOS_2_2
#define __MYUNSUPPORTED_IOS_3_0
#define __MYUNSUPPORTED_IOS_3_1
#define __MYUNSUPPORTED_IOS_3_2
#define __MYUNSUPPORTED_IOS_4_0
#define __MYUNSUPPORTED_IOS_4_1
#define __MYUNSUPPORTED_IOS_4_2
#define __MYUNSUPPORTED_IOS_4_3
#define __MYUNSUPPORTED_IOS_5_0
#define __MYUNSUPPORTED_IOS_5_1
#define __MYUNSUPPORTED_IOS_6_0
#define __MYUNSUPPORTED_IOS_6_1
#define __MYUNSUPPORTED_IOS_7_0
#define __MYUNSUPPORTED_IOS_7_1 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_8_0 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_8_1 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_8_2 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_8_3 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_8_4 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_9_0 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_9_1 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_9_2 __MYUNSUPPORTED
#define __MYUNSUPPORTED_IOS_9_3 __MYUNSUPPORTED

#import <Foundation/Foundation.h>

#undef CF_AVAILABLE
#define CF_AVAILABLE(_mac, _ios) __MYUNSUPPORTED_IOS_##_ios

#undef NS_AVAILABLE
#define NS_AVAILABLE(_mac, _ios) __MYUNSUPPORTED_IOS_##_ios

#undef CF_AVAILABLE_IOS
#define CF_AVAILABLE_IOS(_ios) __MYUNSUPPORTED_IOS_##_ios

#undef NS_AVAILABLE_IOS
#define NS_AVAILABLE_IOS(_ios) __MYUNSUPPORTED_IOS_##_ios

#endif // testing

#import <UIKit/UIKit.h>
xtravar
  • 1,321
  • 11
  • 24
0

it's not integrated into the toolset. one option to test this is to just create a runtime check which would assert (during development while running in newer versions of the os).

assert([<CLASS> instancesRespondToSelector:@selector(potato)]);

then just add that to one of your library's initialization routines.

you could also create a script which would count the number of warnings emitted for a specific translation - if the warning count in question changes then you have updates to make.

justin
  • 104,054
  • 14
  • 179
  • 226
  • Yes, this works for when you know what APIs you need to check. The issue is the unintentional use of 4.x features without these checks. – Ben S Jan 13 '11 at 20:50
  • Hi Ben, I understand the issue, thanks. these are just a few ways to test - how one could approach minimizing the problem. a problem which doesn't have an evident solution. so it would be useful to add the the check when you add a method which you believe could potentially be introduced by apple in the future - not only when you know which apis to check. similarly, you can perform a test for your subclasses by counter-testing when release notes stating api changes/additions are made available. it's not perfect, but it's at least continually automated once configured. – justin Jan 13 '11 at 21:36
-4

No, there is no such warning. However, when you use new API (since you are obviously writing these later), just check the docs when they were available.

Also, if you're supporting 3.0 and using new SDK for development, you must absolutely be testing on actual devices running 3.0

Another thing you could do is write your own utility that parses the availability macros in the headers and then warns you if you're calling anything you shouldn't be.

However, I must reiterate, if you're targeting an older version and using the newer SDK, you must check the docs to see when API became available, and test appropriately.

Jason Coco
  • 77,985
  • 20
  • 184
  • 180
  • 3
    Looking up every single API call is not a reasonable solution. I do agree that 3.0 testing is necessary, but what I'm looking for is a time saving solution where 4.x code gets caught early rather than in testing. – Ben S Jan 13 '11 at 20:48
  • 1
    @Ben: Knowledge of the API that you're writing for is /absolutely/ a reasonable thing. As a professional, you should know how the API of a system you're officially supporting works and what is or isn't available. All of this is documented, and you look it up when you /start/ including new API. If you're not a professional or just doing some hobby work, then simply don't support older versions of the OS. My answer to this question is also not wrong or bad (IMHO), it's what's expected of a developer in any situation. Also, the answer is still the answer: there is no such warning. – Jason Coco Jan 14 '11 at 02:55
  • @Ben: This also leaves you with the same options: parse your code yourself using the availability macros in the framework header files, know the API you're using or look it up if you're unsure, and/or submit a feature request to Apple to basically add a parser for you and warn you. – Jason Coco Jan 14 '11 at 02:57
  • 8
    `[NSThread currentThread] setPriority:1.0]` Look innocuous enough. Doesn't sound or look like a 4.0 only API, but it is. Some of the minor changes/additions in the API diffs are not related to new classes or frameworks. They're just new inclusions from what's been available in MacOS X for years. Knowing all of these by heart is not is not "professional" it's useless memorization, and looking up every API call as I type or before I check my code into version control is a time sink. As a professional I know when frameworks/classes were intoduced, it's these little gotcha calls I want to catch. – Ben S Jan 14 '11 at 16:47
  • 1
    @Ben: You don't have to know these things by heart, that's why the documents are there. If you've never called setThreadPriority: before, it behooves you, as a professional, to check it out. Also, that call /has not been around in Mac OS X for years/. It was introduced with 10.6 and was unavailable before that. Basically iOS 3.0 & 3.1 follow Foundation from 10.5 while 4.0 (3.2 is a kind of special case) follows Foundation from 10.6. When a new release comes out, it is very easy to look over the API diffs to see if there are minor changes to old classes. – Jason Coco Jan 14 '11 at 17:25
  • 3
    I don't understand why you're stuck on the idea that we should memorize the OS that every API was introduced in, and going to the headers or docs every time is just tedious. We're not talking about first use. Do you have every API introduced in 4.3 committed to memory, Jason? This is very easily a check that the compiler could do based on your deployment target. A compiler warning would not be appropriate, of course, seeing as you could have runtime checks for a method's existence, but this would be handy static analyzer addition. – Bryan Henry Oct 22 '11 at 16:45
  • @DiscDev I think we just have different ideas of what professional is. I'm sure you're comfortable going to a doctor who doesn't want to memorize human anatomy, or consult docs when theories change, right? I'm not saying you have to have everything memorized, but as a professional, you should have a professional level understanding of the environment you work in and when things change. At the very least, reading the release notes and having some idea when new constructs are introduced. – Jason Coco May 22 '15 at 09:26
  • @DiscDev Exactly, he'll leverage his tools, she won't rely solely on them. It'd be nice for the tools to be able to do some of that stuff, but IMHO, you're not a professional if you don't keep up with those changes and you don't know about key changes to the API, why they're made, and how they affect your application. I've also seen applications made by those tools that are really bad because the developer relied only on the tools and not on understanding what's going on with the platform. – Jason Coco Jun 02 '15 at 00:32
  • @JasonCoco looks like Apple added the ability in Swift 2 to detect new API calls and warn you automatically. Now I can be more professional with less effort, and Xcode has finally caught up with 2015! – DiscDev Jun 11 '15 at 15:15
  • Yeah, I think it's a lot easier to do in Swift and now everyone can be happy :) – Jason Coco Jun 12 '15 at 21:31