Best practice for storing temporary data in swift

2020-03-03 05:01发布

I have developed an app that gets a lot of JSON data from server and shows on different VCs.What I did was getting data and setting data to static variables, so I can access data in all my app.

Now I noticed that I could have used CoreData or maybe UserDefaults instead of Static variables.

My question is "What is the best practice for this type of apps and why exactly?"

NOTE: The app doesn't need to work offline and save data for showing in offline mode.

5条回答
等我变得足够好
2楼-- · 2020-03-03 05:26

For "small" data like key : value it's best to use UserDefaults. If you have more to store, use CoreDara as this is basically a SQLite database.

查看更多
叼着烟拽天下
3楼-- · 2020-03-03 05:27

Basically I am flowing three option :

Global variable : I need to define Globally struct :

struct GlobalUser {
    static var name : String

}

now I can use simply call the global variable from anywhere in my project :

GlobalUser.name = "Nazmul Hasan"

UserDefaults

it is using for data store but which is not sensitive data :

      //key
       struct defaultsKeys {
        static var name = "name"   
       }

     //Setting 
        let defaults = UserDefaults.standard
        defaults.set("Nazmul", forKey: defaultsKeys.name)

    // Getting
    let defaults = UserDefaults.standard
    if let userName = defaults.string(forKey: defaultsKeys.name) {
        print(userName) // print what you store 
    }

keyChain it is using for data store but which is sensitive data like username, password or tokens.

Save and retrieve value via KeyChain swift

but I am personally recommend to flow this blog that is published by apple

Apple iOS Data Storage Guidelines

more interested read this question thread :

How to save local data in a Swift app?

查看更多
贼婆χ
4楼-- · 2020-03-03 05:38

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.

查看更多
倾城 Initia
5楼-- · 2020-03-03 05:40

You can create data model class to store temporary data from JSON response.

Example of Data Model class : -

class RootClass : NSObject, NSCoding{

    var array : [Int]!
    var booleanField : Bool!
    var custArray : [CustArray]!
    var nullField : AnyObject!
    var number : Int!
    var object : Object!
    var string : String!


    /**
     * Instantiate the instance using the passed dictionary values to set the properties values
     */
    init(fromDictionary dictionary: [String:Any]){
        booleanField = dictionary["Boolean"] as? Bool
        nullField = dictionary["Null"] as? AnyObject
        number = dictionary["Number"] as? Int
        string = dictionary["String"] as? String
        if let objectData = dictionary["Object"] as? [String:Any]{
            object = Object(fromDictionary: objectData)
        }
        custArray = [CustArray]()
        if let custArrayArray = dictionary["CustArray"] as? [[String:Any]]{
            for dic in custArrayArray{
                let value = CustArray(fromDictionary: dic)
                custArray.append(value)
            }
        }
    }

    /**
     * Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
     */
    func toDictionary() -> [String:Any]
    {
        var dictionary = [String:Any]()
        if booleanField != nil{
            dictionary["Boolean"] = booleanField
        }
        if nullField != nil{
            dictionary["Null"] = nullField
        }
        if number != nil{
            dictionary["Number"] = number
        }
        if string != nil{
            dictionary["String"] = string
        }
        if object != nil{
            dictionary["object"] = object.toDictionary()
        }
        if custArray != nil{
            var dictionaryElements = [[String:Any]]()
            for custArrayElement in custArray {
                dictionaryElements.append(custArrayElement.toDictionary())
            }
            dictionary["custArray"] = dictionaryElements
        }
        return dictionary
    }

    /**
     * NSCoding required initializer.
     * Fills the data from the passed decoder
     */
    @objc required init(coder aDecoder: NSCoder)
    {
        array = aDecoder.decodeObject(forKey: "Array") as? [Int]
        booleanField = aDecoder.decodeObject(forKey: "Boolean") as? Bool
        custArray = aDecoder.decodeObject(forKey: "CustArray") as? [CustArray]
        nullField = aDecoder.decodeObject(forKey: "Null") as? AnyObject
        number = aDecoder.decodeObject(forKey: "Number") as? Int
        object = aDecoder.decodeObject(forKey: "Object") as? Object
        string = aDecoder.decodeObject(forKey: "String") as? String
    }

    /**
     * NSCoding required method.
     * Encodes mode properties into the decoder
     */
    @objc func encode(with aCoder: NSCoder)
    {
        if array != nil{
            aCoder.encode(array, forKey: "Array")
        }
        if booleanField != nil{
            aCoder.encode(booleanField, forKey: "Boolean")
        }
        if custArray != nil{
            aCoder.encode(custArray, forKey: "CustArray")
        }
        if nullField != nil{
            aCoder.encode(nullField, forKey: "Null")
        }
        if number != nil{
            aCoder.encode(number, forKey: "Number")
        }
        if object != nil{
            aCoder.encode(object, forKey: "Object")
        }
        if string != nil{
            aCoder.encode(string, forKey: "String")
        }
    }
}

Simple way to create data model class : - jsoncafe.com

查看更多
ら.Afraid
6楼-- · 2020-03-03 05:49

You should not store data to UserDefaults, user defaults is just a key value based file which is mostly used to store some data about user, for example :

doNotShowAdds : true
isUserLogedIn :true.

You should not use keychain either since keychain is encrypted container where u store critic data about user, for example : password.

You don't need to use database

My opinion is that you should not use global variables, mainly I try to make viewController data specific for itself, the data should not be accessed from anywhere in your project. When to use global variables in Swift

Mostly I make service protocols with implementations for specific view controllers. Anyways, each controller should have its own part of data for example

class MyViewControllerOne {
    private var data :Array<DataExampleOne>;
}

class MyViewControllerTwo {
    private var data :Array<DataExampleTwo>;
}

Once you load data from your API, the data will be there, until you close your app.

You can always make another class which will contain this data and maybe predicates for cleaner approach for example :

protocol MyViewControllerOneService {
    func search() -> Array<DataExampleOne>;
}

class MyViewControllerOneServiceImpl :MyViewControllerService {
public var data :Array<DataExampleOne>;

public func search(searchString :String) -> Array<DataExampleOne>{
    let predicate = NSPredicate() ...
    let filteredArray = self.data.filter { evaluate...}
    return filteredArray;
    }
}

I use similar approach, instead of saving data in my service classes (business logic), I got repositories which I use to get data from database. Since you don't have any database this approach is okay.

and then instead of

class MyViewControllerOne {
    private var data :Array<DataExampleOne>;
}

you can use

class MyViewController {
    private var myViewControllerService :MyViewControllerOneServiceImpl;
}

Hope it helps, Best regards!

查看更多
登录 后发表回答