2

I have the following class that describes a country:

class Country: NSObject {

    public var name: String

    static let england = Country(name: "England")
    static let scotland = Country(name: "Scotland")
    static let allCountries = [england, scotland]

    init(name: String) {
        self.name = name
        super.init()
    }

    convenience init?(named name: String) {
        guard let country = Country.allCountries.filter( { $0.name == name } ).first else { return nil }
        self.init(name: country.name)
    }

}

To get the constant Country.england, I would use:

let england = Country.england

However, I want to be able to get this constant from a String using convenience init?(named name: String) as follows:

let england = Country(named: "England")

This seems a little clunky and this code initialises a new "england" instance rather than taking the static one.

Is there a more Swifty way of doing this? Ideally I'd like to just let self = country in the convenience init?(named name: String) function, but I get the error cannot assign to value: 'self' is immutable.

Thanks for any help.

4
  • 1
    KVC with literal strings is not Swifty at all 😉. Commented Sep 3, 2018 at 9:33
  • 1
    Pro tip - use .first(where: { $0.name == name } ) rather than .filter( { $0.name == name } ).first Commented Sep 3, 2018 at 9:34
  • @AshleyMills thanks for the tip Commented Sep 3, 2018 at 9:46
  • @vadian touché ;-) Commented Sep 3, 2018 at 9:51

2 Answers 2

3

If your Country only has a name then you would be better off using an enum…

enum Country: String, CaseIterable {
    case england = "England"
    case scotland = "Scotland"
}

Country(rawValue: "Scotland") // Optional(scotland)
Country(rawValue: "Wales")    // nil

Note CaseIterable is Swift 4.2+ but allows you to write Country.allCases

Country.allCases  // [england, scotland]
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your suggestion. The original post is more of a minimal working example. The class is more complicated than just having one variable.
2

What about writing a named factory method instead?

static func named(_ name: String) -> Country? {
    return Country.allCountries.first(where: { $0.name == name } )
}

// usage:
let england = Country.named("England")!

The initializer approach could be used if Country were a struct:

struct Country {

    public var name: String

    static let england = Country(name: "England")
    static let scotland = Country(name: "Scotland")
    static let allCountries = [england, scotland]

    init(name: String) {
        self.name = name
    }

    init?(named name: String) {
        guard let country = Country.allCountries.filter( { $0.name == name } ).first else { return nil }
        self = country
    }

}

4 Comments

Thanks, I was leaning towards this being the better way, but it would look nicer to initialiser it rather than call a class function in my opinion...
@AlexJ.R.Lewis You could use an initialiser if Country were a struct. See the edit.
That was my next question - does it need to inherit from NSObject and does it need to be a `class?
@AshleyMills I'll look into structs. Perhaps it doesn't need to inherit from NSObject. Country just needs to be equatable and have a bunch of UI* objects.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.