Hallo brandweerlieden en softwareontwikkelaars, Leo hier. Happy New Year! Today let’s talk a little bit about unavailable functions in Swift.
Winter has arrived and with it the cold. It’s time to be at home, cozy and under the blankets. In my homeland, however, things during this time of the year were different because it was like the peak of summer. Temperatures of 35 Celsius were common this time of the year, and now I finally get what all the American movies were depicting. Living in a place where snow and winter try to kill you, gives you a different feeling from all other seasons.
For example, in the summer I have this urge to go out whenever I can because I know that in some months the winter will come, and it won’t be great to be outside anymore. In my home country, I could leave the house without checking the weather app, here it is simply impossible. Even the things that you eat change with the seasons in The Netherlands, for example in the summer we tend to eat lighter foods, and in the winter more fatty food.
I love the place where I live now and the cultural differences are amazing. Each day we discover new things about the culture and we are also learning the local language. Today I learned the verb “vertraging” which means delay, however, you can’t use that verb alone, you need another verb “hebben” to use “vertraging” correctly. For example, “De bus heeft vertraging” ( the bus is delayed ). Cool, isn’t it?
I don’t know about you but for me is amazing learning new languages and starting to understand more and more the culture I’m involved in, as the old saying goes “When in Rome, do as the Romans do“.
Talking about languages, last week we explored the final keyword in Swift. I’ve learned that you can add final to properties and functions, and I thought that was only for class level. That’s why I keep studying, every day I learn new things!
And the week before that we studied the geofence technique using SwiftUI. The geofence technique is the main techncal engine of PokemonGO and it is really interesting how easily you can do that using MapKit and SwiftUI.
No more talking, let’s code! But first…
Painting of The Day
The painting I chose is an art piece from the XVII century called “A Wooded Landscape with Peasants in a Horse-Drawn Cart Travelling Down a Flooded Road” by Jan Siberechts.
The date is unclear, but it’s a fantastic piece from Siberechts’ time in Antwerp. Between 1661 and 1672, he was all about capturing these noble peasants in his paintings. He really took inspiration from the rustic life in Flanders, and honestly, these are some of his best stuff.
Siberechts had this thing for scenes with flooded roads, where you’d see peasants, their cattle, hay wagons, and carts full of veggies, all trying to wade through. It’s like his signature move. But it’s not just about what he paints; it’s how he does it. His skill in genre painting shines through, especially in how he captures the play of light on moving water. Like in this piece, the way the water splashes around the horse and cart, and the light dances on the ripples. Absolutely brilliant.
I chose this painting because the road was unavailable. Got it?
The Problem – How to Make A Method or Function Unavailable in Swift
You have subclass that you want to suppress the use of a superclass function.
Imagine that we have those two classes, Dog and Pomeranian, like the example below:
class Dog { let breed: String init(breed: String) { self.breed = breed } func bark() { print("legacy bark") } func newBark() { print("now we are barking!") } } class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } override func bark() { print("Pomeranian bark!") } } let dog = Pomeranian() dog.bark() // this prints Pomeranian bark!
That prints “Pomeranian bark!”, which is expected.
Just a quick remember of dynamic dispatch in Swift, if you type the constant dog as a type Dog like this:
let dog: Dog = Pomeranian() dog.bark()
It still prints “Pomeranian bark!” because Swift would dynamically decide what the actual implementation of the bark function in this context and call it. In this case the Pomeranian one.
In this contrived example, the Pomeranian dog overrides the bark function of the superclass Dog. Nothing new now.
To make the override unavailable we can ironically use the @available annotation in the function level. Check the example below:
class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } @available(*, unavailable) // add this to the function override func bark() { print("Pomeranian bark!") } }
With this in place, we can’t call the override function directly in the Pomerian objects. For example:
class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } @available(*, unavailable) override func bark() { print("Pomeranian bark!") } } let dog = Pomeranian() dog.bark() // ERROR
You can’t access bark from Pomeranian objects anymore.
However, this annotation has some limitations. You can trick the compiler into executing the bark function from Pomeranian, even with the @available(*, unavailable) annotation.
One way is to upcast the Pomeranian object to the Dog object. And then, calling the bark() function, like this:
class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } @available(*, unavailable) override func bark() { print("Pomeranian bark!") } } let dog: Dog = Pomeranian() dog.bark() // This prints "Pomeranian bark!"
But why does this work? Because of the dynamic dispatch that we discussed earlier. The dynamic dispatch will search for the real implementation of that object function and in this case the Pomeranian one.
So, in any case that we are upcasting Pomeranian to Dog, you can still access the unavailable function from Pomeranian. For example:
class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } @available(*, unavailable) override func bark() { print("Pomeranian bark!") } } let dog = Pomeranian() func makeBark(dog: Dog) { dog.bark() } makeBark(dog: dog) // prints "Pomeranian bark!" // OR (dog as Dog).bark() // prints "Pomeranian bark!"
So, if you are trying a way to not run all the child class implementation you can:
- Comment on the code.
- Leave as an Empty Function
Another Swift feature that you can use to turn parts of the code unavailable is the compiler directive #unavailable, which was introduced by Bruno Rocha, a Brazilian guy! Go Brasil!
Let’s check that feature now.
Comparing the #unavailable Compiler Directive
If we had to rewrite the code above in a way that the override would never be run, you could do something like this:
class Pomeranian: Dog { init() { super.init(breed: "Pomeranian") } override func bark() { if #unavailable(iOS 999) { super.bark() } else { print("Pomeranian bark!") } } }
The code above is just saiyng “if you are running the iOS version 998 or below run the super function, otherwise, run the else statement”. Of course, that is not advisable, because if Apple EVER starts to use iOS version 999 your code would break.
So DON’T DO THIS. The best thing to do would be just to comment on the override implementation if you need to maintain it in your codebase for any reason.
And that’s it for today!
Summary – Unavailability Condition in Swift
In summary, Swift’s @available
annotation and #unavailable
compiler directives are powerful tools for managing function availability. They help ensure code adaptability and maintainability.
However, they must be used wisely to avoid future compatibility issues. These features are not just for restricting access but for fostering a scalable, efficient codebase, ready for Swift’s evolving landscape.
Fellow iOS Developers, that’s all. I hope you liked reading this article as much as I enjoyed writing it.
If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page. You can likewise sponsor this blog so I can get my blog free of ad networks.
Thanks for the reading and…
That’s all folks.
Image credit: Featured Painting