Sajjon Sajjon - 2 months ago 19
Swift Question

Nested indirect enums in Swift

I am writing an AudioManager in Swift 3 (please help me with a better name than "AudioManager") that wraps

AVPlayer
. The aim is to have a player handling interrupts, route change and support custom playback rate, sleep timer, command center, now playing info etc. Since
AVFoundation
is heavily event driven, making use of
KVO
and
NSNotification
it makes sense to make my project event driven as well. I have incorporated a messaging system, where different parts of the app sends events up the chain, all the way up to the root node, AudioManager. So I wanted an event representation as
enum
, because that's what makes most sense. However, I want my events grouped by e.g. RouteChange, BufferingEvent, InterruptionEvent etc. So I finally got it working using nested enums.

I am writing an example GUI, which then can tune in to events from AudioManager, instead of having to use
NSNotification
, or closures.

EDITTED using the answer provided by @andyvn22

enum AudioError: Error {
indirect case buffering(Buffering)
enum Buffering {
case unknown
}

indirect case playback(Playback)
enum Playback {
case failedToSetupAVAsset
case failedToSetupAVItem
}

init(_ buffering: Buffering) {
self = .buffering(buffering)
}

init(_ playback: Playback) {
self = .playback(playback)
}
}

enum Event {
case failure(AudioError)

init(_ error: AudioError) {
self = .failure(error)
}

init(_ bufferingError: AudioError.Buffering) {
self = .failure(AudioError.buffering(bufferingError))
}

init(_ playbackError: AudioError.Playback) {
self = .failure(AudioError.playback(playbackError))
}

indirect case buffering(Buffering)
enum Buffering {
case idle, started, finished
}

indirect case playback(Playback)
enum Playback {
case tick, wasPaused
}

indirect case route(RouteChange)
enum RouteChange {
case unavailable, available
}

indirect case interruption(Interruption)
enum Interruption {
case interrupted, interruptionEnded
}
}


You can paste this all in to a Swift Playground, and add the
handle
methods and calling it with the example calls below:

func handle(_ error: AudioError.Buffering) {
handle(AudioError.buffering(error))
}

func handle(_ error: AudioError) {
handle(Event.failure(error))
}

func handle(_ event: Event) {
switch event {
case .failure(let errorType):
print("failure", terminator: " ")
switch errorType {
case .playback(let error):
print("playback", terminator: " ")
switch error {
case .failedToSetupAVAsset:
print("setupAVAsset")
case .failedToSetupAVItem:
print("setupAVItem")
}
case .buffering(let error):
print("buffering", terminator: " ")
switch error {
case .unknown:
print("unknown")
}
}
case .buffering(let buffering):
print("buffering", terminator: " ")
switch buffering {
case .idle:
print("idle")
case .started:
print("started")
case .finished:
print("finished")
}
case .playback(let playback):
print("playback", terminator: " ")
switch playback {
case .tick:
print("tick")
case .wasPaused:
print("wasPaused")
}
default:
print("unhandled case")
}
}

/* All these are equivalent */
handle(Event.failure(.buffering(.unknown)))
handle(Event(.buffering(.unknown)))
handle(Event(AudioError(.unknown)))
handle(Event(.unknown))
handle(.unknown)


ORIGINAL QUESTION

However, it is a bit tedious to write
handle(Event(.buffering(.unknown)))
which already is the short version of
handle(Event.failure(.buffering(.unknown)))
.

My question:

Is it possible to create an
Event
with
case .failure
using just an inner case inside either AudioError.Buffering or AudioError.Playback ?

Which would allow be to do something like this:

handle(Event(.unknown))


Assuming AudioError.Buffering and AudioError.Playback does not share a case with the same name...

Maybe I have missed some cool part of Swift 3 what would allow this?

Answer

Yes--by overloading initializers, you can allow for initialization by any of the subtypes. For example:

enum AudioError: Error {
    indirect case buffering(Buffering)
    enum Buffering {
        case unknown
    }

    indirect case playback(Playback)
    enum Playback {
        case failedToSetupAVAsset
        case failedToSetupAVItem
    }

    init(_ buffering: Buffering) {
        self = .buffering(buffering)
    }

    init(_ playback: Playback) {
        self = .playback(playback)
    }
}

let example = AudioError(.failedToSetupAVAsset) //this works...
let other = AudioError(.unknown) //but so does this.

By creating many initializers likes these for each subtype, but on Event rather than AudioError, you can nest as much as you'd like without complicating syntax.

Comments