Command line arguments with user defaults
10 Mar 2024You can use UserDefaults
as a simple way to get the arguments passed to an app on launch without having to write any command line parsing.
The basic capability is that you can pass an argument like -example some-string
as a launch argument and this will be readable by defaults like this:
UserDefaults.standard.string(forKey: "example") //=> "some-string"
Supported types
UserDefaults
supports a few types Array
, Dictionary
, Boolean
, Data
, Date
, Number
and String
.
It is possible to inject data and have them be understood in any of these types, the key is recognising that you need to use the same representation that plists use.
Array
Arrays are heterogeneous and can be represented like this
// -example <array><string>A</string><integer>1</integer></array>
UserDefaults.standard.array(forKey: "example") //=> Optional([A, 1])
Dictionary
Any key value pair
// -example <dict><key>A</key><integer>1</integer><key>B</key><integer>2</integer></dict>
UserDefaults.standard.dictionary(forKey: "example") //=> Optional(["B": 2, "A": 1])
Boolean
This can be represented by many variants.
All of <true/>
, 1
and true
will count as true whereas <false/>
, 0
and false
will count as false.
// -example <true/>
UserDefaults.standard.bool(forKey: "example") //=> true
Data
// -example <data>SGVsbG8sIHdvcmxkIQ==</data>
UserDefaults.standard.data(forKey: "example")
.flatMap { String(decoding: $0, as: UTF8.self) } //=> Optional("Hello, world!")
Date
Date doesn’t have a convenience function but still returns an honest date
when encoded correctly
// -example <date>2024-03-10T22:19:00Z</date>
UserDefaults.standard.object(forKey: "example") as? Date //=> Optional(2024-03-10 22:19:00 +0000)
Number
For numbers you have the option to not wrap in any tags and hope for the best or to choose real
or integer
// -example <real>1.23</real>
UserDefaults.standard.float(forKey: "example") //=> 1.23
UserDefaults.standard.double(forKey: "example") //=> 1.23
UserDefaults.standard.integer(forKey: "example") //=> 1
// -example <integer>1</integer>
UserDefaults.standard.float(forKey: "example") //=> 1.0
UserDefaults.standard.double(forKey: "example") //=> 1.0
UserDefaults.standard.integer(forKey: "example") //=> 1
Interestingly if you don’t provide a tag then integer(forKey:)
doesn’t truncate it just returns 0
// -example 1.23
UserDefaults.standard.integer(forKey: "example") //=> 0
String
Strings can simply be passed directly unless you want to do anything more complex in which case you’d want to wrap in <string></string>
tags.
Piecing things together
In some cases you might want to pass more complex data. One such example I came across was wanting to inject in a user profile that has multiple properties for UI testing. I could design the api so that the UI tests would pass multiple arguments but that would require validation and error handling in the app. It would be handy if I could just pass a JSON blob and then decode it inside the app. This is possible with two additional steps
- Wrap the JSON blob in
<string></string>
tags - Escape the XML entities
Let’s imagine I have the following fictitious User
type
struct User: Decodable {
let name: String
let token: String
let isPaidMember: Bool
}
It might be handy in my UI tests to perform a login once at the beginning of all tests to get a token
and then inject it into all the tests.
I can also toggle the status of isPaidMember
in each test.
An appropriate argument that would work would look like this:
# {"name":"Paul","token":"some-token","isPaidMember":true}
-user <string>{"name":"Paul","token":"some-token","isPaidMember":true}</string>
The corresponding code to parse this and fail silently in case of error would look like this
let user = UserDefaults.standard.string(forKey: "user")
.flatMap { try? JSONDecoder().decode(User.self, from: Data($0.utf8)) }
Conclusion
UserDefaults
can make launching your app and injecting data pretty simple.
All your favourite primitive types are supported and once you get your head around using the plist markup you can start passing all kinds of data in just the right format for your needs.