Search code examples
iosswiftstaticplistcodable

Plist to struct class with codable and static variables


I have a Plist which has endpoint URLs in it. I want to read that Plist and write it to my URLs struct after app opens and I want to reach these URLs in everywhere. Therefore, variables needs to be static. However, when I do var's static I get below error;

'static var' declaration requires an initializer expression or getter/setter specifier.

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

        enum CodingKeys: String, CodingKey {
            case urlBaseURL = "url_base_url"
            case urlCheckCMS = "url_check_cms"
            case urlJSON = "url_json"
        }
    }

    // MARK: Convenience initializers

    extension URLs {
        init?(data: Data) {
            guard let me = try? PropertyListDecoder().decode(URLs.self, from: data) else { return nil }
            self = 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? JSONEncoder().encode(self)
        }

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

EDIT: In addition to below answer I change data init class with below code to get rid of optional. With below code, I check plist and If return nil when creating init class app won't continue to work;

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

and

static var instance: URLs!

But I'm still not sure this will be the best approach. As It is discussed in below post;

Loading Configurations from plist into singleton


Solution

  • To fix your compiler errors, you should just provide a default value for your static members:

    static let urlBaseURL: String = ""
    

    However, there are a few major issues with your code. Using static members in a codable struct will not work as you think because coding/encoding always operates on instances of the struct, not on the struct type itself.

    You should remove the static from your vars and instead provide a singleton instance of URLs which then can be read from all your app's components:

    struct URLs: Codable {
      static var instance? = nil
      let urlBaseURL: String
      let urlCheckCMS: String
      let urlJSON: String
    }
    

    In your startup code you can set the value with:

    URLs.instance = URLs(data: yourData)
    

    And later you can access the values like with:

    let urlBaseURL = URLs.instance?.urlBaseURL
    

    Please note that the singleton pattern is commonly criticised, and for good reasons; in your case and for a start I think it's ok though.