Reputation: 749
I store user changeable settings as JSON in the keychain. The app has premium content that is only allowed if the user is subscribed. Depending on the subscription state, the JSONEncoder/Decoder skips some variable and sets it to a default value. The concept is working as expected but there is a situation when the concept isn't optimal.
Say that a user subscribes and changes some of the premium variables. Then the subscription expires and the variables change back to their default values. So far everything's good. But when the user resubscribes and the premium features are enabled again the changes that the user made during the recent subscription is lost. The behavior is expected since I overwrite the JSON data with only the "free" variables. I'm looking for a solution where the "premium" variables are persistent but not accessible (returning default values) for free users. Are there any suggestions for solving this situation?
I have a struct with variables that looks like this:
struct Settings: Codable {
var someFeature:Bool = false
var someSetting = 150
var someURL: URL? = URL(string: "https://www.google.com")
}
When the user changes the settings I save it in the keychain as JSON using JSONEncoder:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// ALWAYS ALLOWED
try container.encode(someURL, forKey: .someURL)
// ONLY ALLOWED FOR PREMIUM USERS
if settingsManager.shared.isPremium {
try container.encode(someFeature, forKey: .someFeature)
try container.encode(someSetting, forKey: .someSetting)
}
}
The settings are loaded in the app from the keychain using JSONDecoder:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// ALWAYS ALLOWED
self.url = try container.decodeIfPresent(URL.self, forKey: .someUrl) ?? URL(string: "https://www.google.com")
// ONLY ALLOWED FOR PREMIUM USERS
if settingsManager.shared.isPremium {
self.someFeature = try container.decodeIfPresent(Bool.self, forKey: .someFeature) ?? false
self.someSetting = try container.decodeIfPresent(Int.self, forKey: .someSetting) ?? 150
} else {
// DEFAULT VALUES FOR FREE USERS
self.someFeature = false
self.someSetting = 150
}
}
Codingkeys:
private enum CodingKeys : String, CodingKey {
case someFeature, someSetting, someUrl
}
Upvotes: 0
Views: 45
Reputation: 139
I can suggest to use wrapped properties. You can store always only user defined properties, and resolve the actual value when access to properties
struct Settings: Codable {
private var someFeature:Bool = false
private var someSetting = 150
var someURL: URL? = URL(string: "https://www.google.com")
private enum CodingKeys : String, CodingKey {
case someFeature, someSetting, someUrl
}
var isSomeFeatureEnabled: Bool {
settingsManager.shared.isPremium ? someFeature : false
}
var someSettingValue: Int {
settingsManager.shared.isPremium ? someSetting : 150
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(someURL, forKey: .someUrl)
try container.encode(someFeature, forKey: .someFeature)
try container.encode(someSetting, forKey: .someSetting)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.someURL = try container.decodeIfPresent(URL.self, forKey: .someUrl) ?? URL(string: "https://www.google.com")
self.someFeature = try container.decodeIfPresent(Bool.self, forKey: .someFeature) ?? false
self.someSetting = try container.decodeIfPresent(Int.self, forKey: .someSetting) ?? 150
}
}
So when you will decode this structure, you will get saved values, when you will try override settings, you will save the previous values. But when get properties values via wrapped properties it will be resolved by value defined in settingsManager.shared.isPremium
If you want to update properties in the settings you can define setter and getter:
var isSomeFeatureEnabled: Bool {
get {
settingsManager.shared.isPremium ? someFeature : false
}
set {
if settingsManager.shared.isPremium {
someFeature = isSomeFeatureEnabled
}
}
}
Upvotes: 2