With all of the new and awesome APIs that come out every year for each successive iOS version, it can be tempting to just drop support for the previous versions of iOS, and just go completely to the new stuff. Of course though, we usually can’t do that for many reasons. As such, we need to support older versions of iOS, and maybe dropping the oldest ones after they just become too difficult to support, or when active users on that platform drop to a certain amount.
There have been ways to check for API availability for some time, by checking if something responds to a selector, or just checking the iOS version ourselves and using that in a normal Swift “if” statement. With the arrival of Swift though, we have an even nicer version of API checking, that is even helped by Xcode itself. This allows Xcode to actually warn you if you try to use something that is unavailable on one of the deployment targets for your Swift app.
A Great Feature in iOS 9
So, after looking at NSHipster’s iOS 9 post, I have noticed one of my favorite new additions to the APIs, NumberFormatter.Style.ordinal. No longer do I have to make the Swift Switch statement to do this myself, and then worry about localization. This does it ALL for me. So Then I drop this into my code like so:
let nf = NumberFormatter() nf.numberStyle = .ordinal let output = nf.string(from: 7) print(output)
And this works great. I run it on my en-US locale machine, and it prints out “7th” (well ‘ Optional(“7th”) ‘, since stringFromNumber returns a Swift Optional String). Of course, my machine is running iOS 9 in the simulator to do this. What happens when I change my minimum deployment target to iOS 8?
In Xcode 6, if I tried something similar with an iOS 8 API trying to run on iOS 7, it would probably compile file, and then crash the iOS 7 device when I tried to run the code only available on iOS 8. As far as it could tell, the Swift code was perfectly valid. The symbol was available in the iOS 8 SDK, which it was building against, and didn’t particularly care about whether iOS 7 knew about it or not.
Checking for API Availability
In Xcode 7 and above though, now you get a build error, simply stating ” ‘ordinal’ is only available on iOS 9.0 or newer” . Straight and to the point. What’s even better, is that it then offers 3 fix-it options to deal with the issue of API availability. The first one is the one that will be used probably the most, replacing it with Swift’s new “if #available” clause. With this, you make a very readable API availability check, to see if the platform version is high enough to know about that part of the API. You can simply select the fix-it option to add it to your code.
You will want to take a look at what the fix-it option does though, because in this case, what it does was incredibly accurate in checking for the version, but did unfortunately miss a finer point:
//This version needs some work let nf = NumberFormatter() if #available(iOS 9.0, *) { nf.numberStyle = .ordinal } else { // Fallback on earlier versions } let output = nf.string(from: 7) print(output)
The only part that REQUIRES being in the if #available, is the usage of the NumberFormatter.Style.ordinal. Everything else is available in earlier versions of iOS. However, if the ordinal style is not set…. what is the stringFromNumber method going to do now? It will just do the default behavior for NumberFormatter. In this case, we’ll probably want to do something more like this:
let output: String? if #available(iOS 9.0, *) { let nf = NumberFormatter() nf.numberStyle = .ordinal output = nf.string(from: 7)!! } else { // Fallback on earlier versions output = myOrdinalFromNumber(7) } print(output ?? "nil")
In this case, if we’re running under iOS 9.0 or above, the API availability check will run the first block of code and get our NumberFormatter with ordinal style working. If not, it will call our own method that would handle writing the ordinal numbers itself for the languages we support. I definitely understand why it performed the fix-it the way it did, it is the part that actually needed an API availability check for this deployment target. I’m just saying to be careful because while the original fix-it would compile, it would not quite be what we wanted.
Within Swift’s “#available()” block, we give a comma separated list of platforms and version numbers, so if you have something that can run on iOS 7 and OSX Mavericks, the statement would look like:
if #available(iOS 7.0, OSX 10.9, *)
Currently, the valid platform options are “iOS”, “OSX”, “watchOS”, and “tvOS” for use in these API availability checks. You can leave it at any level of the semantic versioning for the platform versions, so “iOS 7”, “iOS 7.1”, and “iOS 7.1.2″ are all valid.
This API availability list must end with the asterisk ” * “, basically saying that for any other platforms the if clause will be run on the minimum deployment target set in your project. So if this was a framework, and it could work on watchOS, it will try to do so. I’m guessing it is also there for possible future hardware. That’s just a random guess though.
Annotating Classes or Methods for API Availability
Anyway, there are 2 other options that the fix-it offered to add into our Swift code: to add the “@available attribute to enclosing…” either instance method, or class, in that case, it will insert:
@available(iOS 9.0, *)
Either right above your instance method definition, or at the top of the class itself (depending on which one you choose). If it’s above the instance method, then any entity in a deployment target below the specified one, like iOS 8, that tries to run this method will get the same error we saw earlier. It basically says “This is an iOS 9 and above only method”. In that case, the caller, under iOS 8 would need to use the “if #available”API availability check to use it or fall-back to another way to handle the situation.
In the other case, you mark the whole class as only available to iOS 9, so an iOS 8 app that tries to create an instance of that class will get a similar warning, thus making the user have to use the “if #available” statement there, and use some other class to handle that situation for older devices. These definitely have their place, and probably are how Apple handles API availability under the hood themselves.
Conclusion
With language-level support in Swift for checking API availability, your code is even safer running on older devices. I am particularly happy about it giving build errors to make sure that YOU KNOW whether this will run under previous platform versions or not. The fix-its are also great additions, giving you a good starting point to letting your code take advantage of new API features, but not leaving previous versions in the dust.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!