Discussion:
"What is the best practice for this type of apps and why exactly?" is a very good question. It would help if you described what type of app you are developing.
Since you only seem to need/want the data while the app is running then I guess the best way is to keep it in memory on some singleton or static variables which you are already doing.
But if you don't want to store your data after the app is relaunched then you might want your data to expire after some duration since app may be opened for a very long time.
Then the question is how you want to receive your data in runtime. Assume you have cached data and you wish to refresh the data anyway. Then you may design your data management so your closure to fetch items may be call multiple times. Assume having something like this:
func fetchUsers(callback: ((_ users: [Any]?, _ isFromCache: Bool) -> Void)) {
if let cachedUsers = MyCache.fetch(.users) {
callback(cachedUsers, true)
}
APIManager.fetch(.users, { users, error in
if let users = users {
MyCache.insert(.users, items: users)
}
callback(users, false)
})
}
Then let's say you want to show these users in some table view you would do something like:
func refreshData() {
self.startActivityIndicator() // Start some minor activity indicator
fetchUsers { (users, isFromCache) in
self.users = users // Assign users whatever they are
if users == nil {
// Show error here
}
if isFromCache == false {
self.stopActivityIndicator() // Only stop activity when fresh data were received so user know he may expect the UI to change
}
tableView.reloadData()
}
}
This will design your user interface part of the data fetching but has nothing to do with where and how your data is stored. For instance MyCache
should still chose weather the cached response is still valid or outdated. And it is MyCache
which decides to either store data into some database or only in-memory.
Some storing procedures:
To keep the design in previous snippets you may have a static class MyCache
which has an string enumeration for stored types. It then has a static dictionary [String: Any]
which contains cached data so a fetch method looks something like
func fetch(type: FetchType) {
return cacheDictionary[type]
}
The insertion is pretty similar then.
To add date (or any other data in there) you may define it as [String: [String: Any]]
and use static keys so then your data would be cacheDictionary[type]["date"] as? Date
and data as cacheDictionary[type]["data"]
.
You may then expand your cache to for instance use user defaults like:
func fetch(type: FetchType) {
if let inMemory = cacheDictionary[type] {
return inMemory
} else if let inDefaults = UserDefaults.standard.object(forKey: type) {
cacheDictionary[type] = inDefaults // Keep it in memory for net time
return inDefaults
} else {
return nil
}
}
This is for storing direct JSON data or objects. Same can be applied for storing data (serializing it with JSONSerializer
) into files. You could create a base path in temporary directory or in library then use file names by type
. Temporary directory may be cleared by the system which you might want and library directory will be persistent.
Then there is database:
The database is a whole other story. It makes no sense to apply a database into the same procedure as above because it is an overkill and you use no benefits of the database at all.
For database you would create models for each of your responses and insert the data into database then notify your UI controllers to update. That means callbacks are not the best practice but rather delegates. You would have for instance DatabaseManager
and on view did appear you would call DatabaseManager.shared.delegate = self
then Users.fetchNewUsers()
and waited for the delegate to trigger. Then on delegate you would fetch objects from database or create a fetch result controller...
There probably very many other ways but I hope this will give you some idea about how to store data when to use which.