Migrating UserDefaults to App Group
June 23, 2019
Recently, I decided to work on a few features for Steps+ that essentially require a lot of my the App's data be in an App Group.
I began the endeavor by doing a quick Google search, which led me to a solution that I ended up modifying slightly and shipping. Here is the snippet I found.
As for the code I ended up shipping to solve this problem, It ended up looking like this:
As you can see, the migration code is isolated into its own class - this allows me to more easily reason about the code and write some tests for the code in isolation.
My strategy was to create a new "App Group" static let to return the new UserDefaults and use the migrator in there when that is created the first time, like so:
I can then just use
I mentioned tests earlier, and having this code in it's own migrator class - while also making this more re-useable and isolated, also lets me right tests such as this one I have:
And thats it! It's always a good idea to search around for solutions/thoughts on problems that you are solving. But remember, if you use/modify code you find - you should understand it and give credit! Thanks for reading/checking this small post out, if you want to support this blog - check out my step tracking app, Steps+.
I began the endeavor by doing a quick Google search, which led me to a solution that I ended up modifying slightly and shipping. Here is the snippet I found.
As for the code I ended up shipping to solve this problem, It ended up looking like this:
final class Migrator: NSObject {
private let from: UserDefaults
private let to: UserDefaults
private var hasMigrated = false
init(from: UserDefaults, to: UserDefaults) {
self.from = from
self.to = to
}
// Returns the proper defaults to be used by the application
func defaults() -> UserDefaults {
return to
}
func migrate() {
// User Defaults - Old
let userDefaults = from
// App Groups Default - New
let groupDefaults = to
// Don't migrate if they are the same defaults!
if userDefaults == groupDefaults {
return
}
// Key to track if we migrated
let didMigrateToAppGroups = "DidMigrateToAppGroups"
if !groupDefaults.bool(forKey: didMigrateToAppGroups) {
// Doing this loop because in practice we might want to filter things (I did), instead of a straight migration
for key in userDefaults.dictionaryRepresentation().keys {
groupDefaults.set(userDefaults.dictionaryRepresentation()[key], forKey: key)
}
groupDefaults.set(true, forKey: didMigrateToAppGroups)
}
}
}
As you can see, the migration code is isolated into its own class - this allows me to more easily reason about the code and write some tests for the code in isolation.
My strategy was to create a new "App Group" static let to return the new UserDefaults and use the migrator in there when that is created the first time, like so:
@objc extension UserDefaults {
private static let migrator = Migrator(
from: .standard,
to: UserDefaults(suiteName: "group.steps.plus") ?? .standard)
@objc static let appGroup: UserDefaults = {
migrator.migrate()
return migrator.defaults()
}()
}
I can then just use
appGroup
instead of the standard
in the app to access or pass around into my methods that require access to User Defaults. I mentioned tests earlier, and having this code in it's own migrator class - while also making this more re-useable and isolated, also lets me right tests such as this one I have:
func testMigrationWorks() {
let firstDefaults = UserDefaults.init(suiteName: "\(Date())")!
firstDefaults.set(10093, forKey: "MYKEY")
let secondDefaults = UserDefaults.init(suiteName: "\(#function)")!
let migrator = Migrator(from: firstDefaults, to: secondDefaults)
migrator.migrate()
XCTAssert(secondDefaults.integer(forKey: "MYKEY") == 10093)
}
And thats it! It's always a good idea to search around for solutions/thoughts on problems that you are solving. But remember, if you use/modify code you find - you should understand it and give credit! Thanks for reading/checking this small post out, if you want to support this blog - check out my step tracking app, Steps+.