A Collection of Interesting Swift Features

In no particular order, here are a few intriguing language features and possibilities I’ve come across over the last few years. Some might seem obvious, some you’ll never need to use… hopefully there’s one or two things here that you find useful or interesting.

1. Custom Subscript Notation

Dictionaries and arrays use square brackets to simplify access. It is quite possible to declare your own subscript accessors too:

class MovieCinemaSeatModel {
    subscript (row row: Character, column column: Int) -> MovieTicket? {
        // Return logic goes here. Note the doubled parameter names -
        // this forces callers to label their arguments.
    }
    subscript (ticketId: String) -> MovieTicket? {
        // This subscript will not require a parameter name when called.
    }
}

let ticketBySeat = movieSeating[row: "C", column: 3]
let ticketById = movieSeating["1234"]

By the way, the Swift standard library contains more subscript patterns that you might realise, like the default value variant of the usual Dictionary accessor.

2. Variadic Parameters

Although arrays can be used instead, a variable number of function arguments can be expressed via ellipses. This results in slightly nicer call site notation, with the caveat that ordinary arrays cannot be used to fulfill a variadic parameter.

func purchaseMovieTickets(for showings: Movie...) {
    /// showings can be treated as an array internally.
}

func refundMovieTickets(_ ticketIds: [String]) {
    /// Unlike the former, this function will accept arrays
    /// at the call site.
}

purchaseMovieTickets(for: showingOne, showingTwo)
refundMovieTickets([ticketIdOne, ticketIdTwo])

3. Single Line Guard Statements

Often guard statements are chained, or styled so that the else scope spans three lines. However, single line guard statements can be a very clear, sequential way of structuring code.

func findNextFilmShowtime() -> Date? {
    guard let films = getFilmList(orderedBy: .showtime) else { return nil }
    guard let nextMovie = films.first else { return nil }
    return findShowtimes(for: nextMovie).first
}

4. Functions as Variables

As in languages like Javascript, Swift functions can be treated like variables - and this doesn’t just apply to any declared closures.

func displayDate(fromNow: Bool) {
    let dateProducer = fromNow ? Date.init : findNextFilmShowtime
    prepareAndThenDisplay(dateProducer)
}

5. if case let Statements

It is possible to match against a single Swift enum (as opposed to switching through each case as is usual). This can be done using an if case let statement.

let movieRating = MovieRating.restricted(age: 16)
if case let .restricted(_) = movieRating {
    // This will run. You can enter associated values
    // to match at a more fine grained level too.
}

6. Indirect enum Cases

Swift enums can associate types with each case (this is an incredible useful feature itself). However, sometimes you might wish to associate enums in a recursive way - one common use case might be in a node like data structure. This presents an impasse to the compiler: it needs to know how much memory to allocate to each enum case, and a recursive relationship could result in an infinite amount of memory being allocated.

This problem would be quite solvable by wrapping recursively associated values with a thin wrapper type, effectively providing a layer of indirection to the compiler. However, Swift allows us to do this without any wrapper type trickery needed.

enum MovieGenre {
    case action, romance, drama
    indirect case comedy(subtype: MovieGenre?)
}

7. Direct Usage of Optional Types

Swift optional types are implemented in the language itself. An optional is just an enum with two cases: .none, and .some(Any). You can directly reference these types, although it isn’t very often that this is actually useful.

if Optional(MovieGenre.action) is Optional<MovieGenre> { print("It certainly is") }

switch Optional<Bool>.init(nilLiteral: ()) {
    case .some(_): break // This won't run
    case .none: break // This will
}

8. Overriding via Extensions

Extensions can not only extend a class - they can override methods too. I’d suggest thinking twice, or perhaps many more times before doing this… modifying classes in this way can result in behaviour that is very tricky to track down.

extension UITableViewController {
    open override func viewDidLoad() {
        // Is this really such a good idea...?
    }
}

9. Lazily Instantiated Variables

Declaring a non optional property, with a dependancy on self (perhaps as a delegate) can prove to be tough. The lazy keyword can help here:

class MovieRatingView: NetworkResponseDelegate {
    lazy var networkProvider = { [unowned self] in
        // Unowned prevents retain cycles. Weak is not
        // necessary since networkProvider cannot be created
        // after the owning self is deinitialized
        return NetworkProvider(responseDelegate: self)
    }()
}

10. Exact Equivalence Operator

Some things can be more equal than other things… under the hood, this checks that objects not only match (via their Equatable conformance), but that the underlying memory address is identical.

let this = Movie(id: 123456)
let that = Movie(id: 123456)

print(this == that) // true
print(this === that) // false
print(this === this) // true

11. Loops can be Labelled

It’s not often you need to do this, but in confusing situations, labels can make the intent of your code very clear.

outerLoop: for tvShowId in 0...10000 {
    innerLoop: for seasonNumber in 0...100 {
        guard let show = TVShow(id: tvShowId) else { continue outerLoop }
        guard let season = TVShow.Season(number: seasonNumber) else {
            break innerLoop
        }
    }
}

12. Unsafe Operators

Unlike some other languages, Swift isn’t so naïve about manipulating numeric types outside the range they support. In fact, bounds errors result in a runtime crash. However, sometimes you might wish to keep the unsafe, bound wrapping behavior - you can do this with unsafe operators. Most numeric operators have unsafe equivalents, found by prefixing the safe operator with an & symbol.

Int.max + 1 // Int overflow runtime error
Int.max &+ 1 // Int.min

13. Private Setters

Sometimes, you may wish for a property to be publicly readable, but only privately writable. You can do this!

class Actor {
    let id: Int
    private(set) var stageName: String
    init(id: Int) { stageName = getStageName(forActorId: id); self.id = id }
    func updateProperties() { stageName = getStageName(forActorId: id) }
}

let andySamberg = Actor(id: 99)
print(andySamberg.stageName)
andySamberg.stageName = "Andrew" // Compile error

14. Functions that Don’t Return

This is a special return type, signalling to the compiler that the method will never return.

func forceCrash(forReason reason: String) -> Never {
    print(reason)
    abort()
    // Note the lack of a return statement
}

15. Closure Capture Blocks

Variables used inside a closure are evaluated at the time that the closure is run. If you’d like to evaluate them at the time the closure is declared, this is quite possible using a closure capture block.

class MovieCollectionViewCell: UICollectionViewCell {
    private class ReuseIdentifier {}
    private var coverImageReuseIdentifier: ReuseIdentifier?
    func displayCoverImage(with url: URL) {
        coverImageReuseIdentifier = .init()
        URLSession.shared.dataTask(with: url) { [weak self,
          coverImageReuseIdentifier] data, response, error in
            // Prevent cell reuse image issues. This is one
            // way of doing it... (there are plenty of other ways too).
            // The reuse identifier is captured when the closure is
            // defined, allowing us to compare it with the non captured
            // variant - to check that the cover image hasn't been updated
            // in the meantime.
            guard self?.coverImageReuseIdentifier
              === coverImageReuseIdentifier else { return }
        }
    }
}