Swift CLI Tools User Preferences
It's common for apps to store user preferences and settings, the same is true for scripts:ssh
needs a place to store the user keys, git
a place to store the user name and email, etc.
While many CLI tools store such data in dot files, in this article let's explore how we can use Foundation's UserDefaults!
UserDefaults
In the surface there's no real difference between using UserDefaults in a script or an app, for example:
- we can read and set any data in the UserDefault's
standard
object:
import Foundation
let userDefaults = UserDefaults.standard
userDefaults.set(5, forKey: "stars")
print(userDefaults.integer(forKey: "stars")) // prints "5"
The
main.swift
content.
- we can create and use different suites:
import Foundation
guard
let userDefaults = UserDefaults(suiteName: "five.stars")
else { exit(EXIT_FAILURE) }
userDefaults.set(5, forKey: "stars")
print(userDefaults.integer(forKey: "stars")) // prints "5"
The
main.swift
content.
- we can even observe for
UserDefaults
changes:
extension UserDefaults {
@objc dynamic public var stars: Int {
get { integer(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
guard
let userDefaults = UserDefaults(suiteName: "five.stars")
else { exit(EXIT_FAILURE) }
let token: NSKeyValueObservation = userDefaults.observe(
\.stars,
options: [.initial, .new]
) { defaults, _ in
let rating = defaults.stars
let fullStars = String(repeating: "★", count: rating)
let emptyStars = String(repeating: "☆", count: 5 - rating)
let stars: String = fullStars + emptyStars
print(stars)
// 👆🏻 prints the initial value and whenever a new change is made.
}
RunLoop.current.run()
// 👆🏻 stops the script from terminating
The
main.swift
content.
The script in action
UserDefaults Location
Regardless of the platform we're running on, UserDefaults are always stored in Property List files, which are XML files in disguise, where the elements alternate between key
tags and other elements types.
This explains why we can store only just a few handful types in UserDefaults.
In iOS apps, the standard
UserDefaults plist is stored in the app home directory under the Library/Preferences
folder: the name of the file is the app bundle identifier, for example blog.fivestars.app.plist
.
On macOS, all user applications UserDefaults are stored at ~/Library/Preferences
, therefore this folder not only contains our app plist file, but all other apps as well.
This is one reason why our apps need to have an unique bundle identifiers.
This works great for apps, but our scripts don't have any bundle identifier: where the UserDefaults data of our tools are stored? It turns out that scripts also use the same folder: instead of using a bundle identifier, the script standard
UserDefaults plist is stored under the name of the script.
e.g. a Swift executable named
hello
will have itsstandard
UserDefaults stored at~/Library/Preferences/hello.plist
.
What about UserDefaults suites? In this case the suite name will be the name of the plist file.
e.g. a script using
UserDefaults(suiteName: "five.stars")
will have this suite data stored at~/Library/Preferences/five.stars.plist
.
Scripts are unsandboxed processes, hence they can read/write any UserDefaults file located in ~/Library/Preferences/
, all it takes is to know the plist name (a.k.a. the app bundle id):
want to read/edit the user preferences for...
- Xcode? Use
UserDefaults(suiteName: "com.apple.dt.Xcode")
- Finder? Use
UserDefaults(suiteName: "com.apple.finder")
.
- Etc.
For an easy way to explore even more preferences of both system and 3rd party apps, I suggest to use the free Prefs Editor app.
Conclusions
In this article we've explored the behind the scenes of our scripts UserDefaults preferences, where they're persisted in our machines, and how scripts can actually access to all apps UserDefaults.
Do your CLI tools store any preferences? What kind of preferences do you store? Do you use UserDefaults? Please let me know!
As always, thank you for reading and stay tuned for more articles!