1

I have a singleton:

public class Manager {
  static let shared = Manager()
  private init() {
    ...
  }
}

Other class can access it by:

Manager.shared

No problem here. Now, I start thinking, what if the private initializer is throwable?

public class Manager {
   // Compiler error: Call can throw, but erros cannot be thrown out of global variable initializer 
   static let shared = Manager()

   // throwable initialier
   private init() throws {
     ...
   }
}

How to resolve above error if I still want Manager to be singleton?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Leem
  • 17,220
  • 36
  • 109
  • 159
  • 3
    Why would you ever want a singleton to not initialise ? this sounds like your trying to tell wether there is a user 'logged in' ? or something ? cant you just have a method returning the result you need? – Sean Lintern Nov 17 '17 at 10:05
  • 2
    What do you expect to happen when initialisation fails? – Hamish Nov 17 '17 at 10:11
  • 1
    `static let shared: Manager? = try? Manager()` may help on you, if you want a direct crash instead of `nil`, you can use `try!` instead – both seems pointless concepts in a case of singleton as your design-pattern idea makes no sense at all. – holex Nov 17 '17 at 10:31

3 Answers3

4

One of the many possible solutions could be to let the shared variable be an Optional and call the initializer with the try? keyword.

Like this:

public class Manager {
   static let shared: Manager? = try? Manager()

   // throwable initialier
   private init() throws {
     ...
   }
}

EDIT: If you want to try to recreate the object if it failed the last time you called the init, you can write something like this.

public class Manager {
    static var shared: Manager? {
        get {
            if instance == nil {
                instance = try? Manager()
            }
            return instance
        }
    }

    static private var instance: Manager? = try? Manager()

    // throwable initialier
    private init() throws {
        ...
    }
}
ThreeDeeZero
  • 86
  • 1
  • 4
  • 1
    what is the terminology for "try?" – Leem Nov 17 '17 at 10:02
  • 1
    @Leem it will set shared to nil, if try block throws an exception. https://stackoverflow.com/questions/32390611/try-try-try-what-s-the-difference-and-when-to-use-each – denis631 Nov 17 '17 at 10:03
  • 1
    I guess it's not what the Leem wants. Since the exception/error will be thrown only on the first call. The further calls will return nil and not try to call the throwable initializer. Leem has to better describe the intention of the shared call – denis631 Nov 17 '17 at 10:06
  • @denis631 You are right, it probably makes more sense to retry to create the instance each time you try to access the singleton. Making an edit that takes that into account. – ThreeDeeZero Nov 17 '17 at 10:11
  • 3
    Note that your second example isn't thread safe (whereas the first is), which may or may not be an issue. – Hamish Nov 17 '17 at 10:27
0

Getting a singleton instance means you don't know if its already initiated or not. If its throwable that means you wants to track.

Well here is a solution you can follow.

enum SomeError: Error {
    case unknown
}

public class Manager {
    private static var sharedManger: Manager? = {
        do {
            let manager = try Manager()
            return manager
        } catch {
            return nil
        }
    }()
    private init() throws {
        ...
    }

    class func shared() throws -> Manager {
        if let sm = sharedManger {
            return sm
        } else {
            throw SomeError.unknown
        }
    }
}

Now you can do something like

do {
    try Manager.shared()
} catch {
    print("Throw")
}
Hussaan S
  • 271
  • 1
  • 12
0

THe error you are seeing is very clear. If you don't understand it, I suggest you to read the docs for throwing functions here.

There are several ways to fix this issue, the thing is - why the heck would you mark initialiser for singleton object throwing... One thing you could do is to wrap up the initialiser into setupManager function like this:

public class Manager {

    static let shared = setupManager()

    // throwable initialier
    private init() throws {

    }

    static func setupManager() -> Manager {

        do {

            return try Manager()
        } catch let error {

            fatalError("error:\(error)")
        }
    }
}

In shorter declaration without the setup method:

public class Manager {

    static var shared: Manager {

        do {

            return try Manager()
        } catch let error {
            // Immediatly stops program execution...
            fatalError("An error occured: \(error)")
        }
    }

    // throwable initialier
    private init() throws {

    }
}

The other way to solve this problem would be to use try? and make the shared instance optional (found in the documentation I posted above):

public class Manager {

    static let shared: Manager? = try? Manager()

    // throwable initialier
    private init() throws {

    }
}

Other way around this could be to force unwrap it, but that's not what you want to do since it's really bad approach to problems...

Please keep in mind that always when you code something, fix the core of the problem, not the symptoms, it can always get worse. I don't see any reason for making singleton initialiser throwing in any case, so if you are dependent on some code in the app which should be handed to it, rethink your architecture. Questions are welcome. Wish happy coding!

Dominik Bucher
  • 2,120
  • 2
  • 16
  • 25