iOS Key Value Pair Persistence Made Simple. And Cool! And Cool. And Cool!
Working with persistence sometimes can be very cumbersome. Or if its not difficult to make reusability to the most. Here you’ll know some hack to make it easier for you and your next project. Read on!
The current context of key value pair (KVP) persistence involves making use of Keychain and UserDefaults. These two API provide interface for developers to store some information in KVP manner.
But Keychain and UserDefaults have different usage and use it in different situation repeatedly make it harder for developer to remember which value stored in Keychain or UserDefaults. It’s not funny to use if
branching each time application try to retrieve a value.
// PSEUDO CODE
if let VALUE = KEYCHAIN.get_for: KEY
// DO SOMETHING
else if let VALUE = USER_DEFAULTS.get_for: KEY
// DO SOMETHING TOO
Off course putting this routine inside an utility class can be an hack, but other developer will get no idea and amount of confidence to reuse your solution.
You will want to use another approach where everybody can read where value stored and retrieved. And can be extended.
The Hacks
First. You need to declare what kind of Key Value Pair you want to use. Here we use String as Key and Value type.
protocol KeyValuePairPersistence {
func write(value: String, key: String) throws
func read(key: String) throws -> String?
}
The UserDefaults and Keychain persistence class will conforms to this protocol.
class UserDefaultsPersistence: KeyValuePairPersistence { ... }class KeychainPersistence: KeyValuePairPersistence { ... }
Wait. Why does the protocol methods need to be marked throws
?
The short answer is Keychain mechanism throwing error in case failure happens. We need to propagate it so application can handle such failure instead of silently print to log. If you have different opinion, I am happy to know.
Second. We will create a list of operation involves KVP in enum.
enum KeyValuePersistenceSession {
case userlogin
case userloginCount
case userPreference
var key: String {
return "\(self)"
}
}
We decide the key from the enumeration itself. Beautiful.
Third. Create Persistence Manager.
This class will be our proxy to performs any KVP operation.
class PersistenceManager {
private userDefaultPersistence = ...
private keychainPersistence = ...// This method is just a helper
private func resolvePersistence(
for session: KeyValuePersistenceSession
) -> KeyValuePairPersistence { ... }
func write(session: KeyValuePersistenceSession, value: String) throws
{ ... }
func read(session: KeyValuePersistenceSession) throws -> String?
{ ... }
}
resolvePersistence
will be the director for our real persistence operation. It will decide which persistence to be used for which case.
...(for session: KeyValuePairPersistence) -> KeyValuePairPersistence {
switch session {
case userlogin:
return keychainPersistence
case userloginCount, userPreference:
return userDefaultsPersistence
}
}
And we call this method inside write
and read
method.
{
let persistence = resolvePersistence(for: session) // WRITE
try persitence.write(key: session, value: value) // READ
return try persitence.read(key: session)
}
Usage Example
let pm = PersistenceManager()try pm.write(session: .userlogin, value: "FB_TOKEN")
try pm.write(session: .userloginCount, value: "3")let color = try pm.read(session: .userPreference)
Now we don’t worry to forgot where a value is should be stored and retrieved from.
Conclusion
Above approach has some flaws that can be enhancen for maximum extendability. But I’ll leave it for you. The goal to avoid having to remember to store in UserDefaults or Keychain is achieved. But you can, of course, add more classes if you want, for example, to store key value pair in file or in remote server. As for now, I hope this approach will open new ideas how to do persistence in the best way using maximum feature Swift and iOS has provided for us. Keep eating. Eh. Coding.
I’m hungry.