2
\$\begingroup\$

I have a plist file which has URLs in it and struct class is feeding from this plist. This struct class has static variable which I use to reach URLs in every part of the app. My question is that I'm defining this static instance with "!". I can also define it with optional ("?"). I'm not sure If I'm declaring singleton variable in right way.

Note: If something happens with reading Plist or creating struct class, app needs to display a message or something then close. So reading successfully is mandatory.

    struct URLs: Codable {
    let urlBaseURL: String
    let urlCheckCMS: String
    let urlJSON: String

    static var instance: URLs!

    enum CodingKeys: String, CodingKey {
        case urlBaseURL = "base_url"
        case urlCheckCMS = "check_cms_url"
        case urlJSON = "table_json_url"
    }

}

// MARK: Convenience initializers

extension URLs {

    init?(data: Data) {
        guard let me = try? PropertyListDecoder().decode(URLs.self, from: data) else { return nil }
        self = me
        URLs.instance = me
    }

    init?(_ plist: String, using encoding: String.Encoding = .utf8) {
        guard let data = plist.data(using: encoding) else { return nil }
        self.init(data: data)
    }

    init?(fromURL url: String) {
        guard let url = URL(string: url) else { return nil }
        guard let data = try? Data(contentsOf: url) else { return nil }
        self.init(data: data)
    }

    var plistData: Data? {
        return try? PropertyListEncoder().encode(self)
    }

    var plist: String? {
        guard let data = self.plistData else { return nil }
        return String(data: data, encoding: .utf8)
    }
}

If I try to declare singleton as usual way;

static let instance = URLs()

It gives below error;

Cannot invoke initializer for type 'URLs' with no arguments

So I get rid of this error by writing a default init method in which I read plist file and write it to instance. However, I'm not sure with this.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Generally a singleton is declared as

    static let shared = URLs()

or for lazy instantiation

    static let shared = { URLs() }()

I would suggest naming the static property shared. That's a convention used throughout the standard library and Foundation.

In your case, if you change the value of instance, I'd use an optional. Your app init method (such as applicationDidFinishLaunching) should instantiate the instance of URLs with a guard and display the message if it fails.

If you are only setting the value once, I'd suggest something like

static var instance: URLs! { willSet { 
    guard instance == nil else { fatalError("value has already been set") }
} }

I normally only use unwrapped optionals when working with an Objective-C based class where I can't overload the initializer (such as view controllers and XCTestCase). In those cases, you know viewDidLoad(), awakeFromNib(), or setUp() will be called (or something is seriously wrong and it isn't safe to continue execution).

Over the last year I've switched to using dependency injection instead of referencing singletons. I even use DI for things like NSNotificationCenter and NSUserDefaults. That makes unit testing a lot easier.

\$\endgroup\$
2
  • \$\begingroup\$ I think I can't be clear fully. When I declare singleton as you tell (both two way) It says "cannot invoke initializer for type with no arguments" because at first url properties are not declared. They are fetched from a plist file. \$\endgroup\$ Commented Jan 25, 2018 at 6:13
  • \$\begingroup\$ I edit my question and add more detail, can you please check it. Thank you. \$\endgroup\$ Commented Jan 25, 2018 at 6:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.