Search code examples
iosswiftuserdefaults

Swift Struct extension


I have a Settings struct that stores hundreds of settings in UserDefaults. Code below:

struct Settings {

public enum key: String
{
    case mySetting = "mySetting"
    ...
    ...

}

 public var mySetting: Bool {
    get {
        return UserDefaults.standard.bool(forKey: key.mySetting.rawValue)
    }

    set (newValue) {
        return UserDefaults.standard.set(newValue, forKey: key.mySetting.rawValue)
    }
}

 ....
 ....
}

I want to extend it. I want to fetch settings of another instance of the app running on a peer device and store it as a Dictionary. mySetting must them return the value of a key.mySetting key from that dictionary rather than UserDefaults. But if there is no peer device connected, then it should return the value in UserDefaults.

I am wondering how I can extend the Settings struct (or perhaps change it to class if needed) to achieve this functionality without putting 'if then else' in hundreds of places in the code.

I do this in hundreds of place:

 var settings = Settings()

Or

 let settings = Settings()

Some construct that automatically pulls the right settings dictionary to look for would be best.

Edit: As I said not all return types can be simple which makes the job harder. Some return types are enum, some dictionary, some Array. As some suggest to have one unified method that returns Any?, the job would be harder at places where I use the return type, for instance.

  public var microphone: Microphone {
    get {
        if let mic = Microphone.init(rawValue: UserDefaults.standard.integer(forKey: key.microphone.rawValue)) {
            return mic
        } else {
            return .auto
        }
    }

    set (newValue) {
        UserDefaults.standard.set(newValue.rawValue, forKey: key.microphone.rawValue)
    }
}

Solution

  • I would add an optinal property for the dictionary and then using a pair of get and set functions either use the dictionary or UserDefaults

    var peerDevice: [String: Any]?
    
    func setting(forKey key: Key) -> Any? {
        if let dictionary = peerDevice {
            return dictionary[key.rawValue]
        }
        return UserDefaults.standard.value(forKey: key.rawValue)
    }
    
    mutating func set(_ value: Any, forKey key: Key) {
        if peerDevice != nil {
            peerDevice?[key.rawValue] = value
        } else {
            UserDefaults.standard.set(value, forKey: key.rawValue)
        }        
    }
    

    Example of wrapper method for enums using raw values

    mutating func setEnum<T>(_ value: T, forKey key: Key) where T: RawRepresentable {
        set(value.rawValue, forKey: key)
    }
    
    func setting<T>(forKey key: Key, andEnumType: T.Type) -> T? where T: RawRepresentable {
        if let value = setting(forKey: key) as? T.RawValue {
            return T.init(rawValue: value)
        }
        return nil
    }
    

    I took the liberty to rename the enum from key to Key

    Then use it like this

    var settings = Settings()
    
    settings.setSetting(true, forKey: .mySetting)
    
    if let flag = settings.setting(forKey: .mySetting) as? Bool {
        print(flag)
    }
    
    settings.setEnum(Microphone.mic2, forKey: .mic)
    
    if let mic = settings.setting(forKey: .mic, andEnumType: Microphone.self) {
        print(mic)
    }