I am developing a game in Swift using UIKit. I want to save/load the changes each time the game is closed and opened. Data need to be saved/loaded are classes containing String and Int's.
Sample class:
class UserData {
var username: String = String()
var gamepoints: String = String()
var items: [UserItem] = []
}
class UserItem {
var name: String = String()
var quantity: Int = 0
var price: Int = 0
}
I only need to save classes similar to ones above. What method should I use? How do I retrieve the updated values of the instance of the UserData class when the game is closed and opened again? Is there a method which I can save this class values to somewhere and initialize them after restarting the app?
The answer is yes. You're going to want to look into NSCoding and NSKeyedArchiver. Basically you will need to do the following steps:
Have your UserData and UserItem classes implement the NSCoding protocol.
Write them to disk when you need to using NSKeyedArchiver
Read them from disk when you need to using NSKeyedUnarchiver
Checkout this article about it: http://nshipster.com/nscoding/
However you have many other options for storing user data - you could also store user data using NSUserDefaults, CoreData, SQLite, etc. Your question is really quite broad, and without knowing the specifics of your game: how often it needs to read/output this data, how much of it there is, what data structures you're using to collect it... it's hard to give you a definite right answer.
Related
I'm making an app that has a collection view of plants. I have Plant class that implements the NSCoding protocol so I can save them. The plants are stored in an array called plantList.
var plantList = [Plant]
And here's what I store in my Plant class:
class Plant: NSObject, NSCoding {
var name: String
var species: String
var nextWateredDate: Date
var wateringPeriod: Int
var wateringCount: Int
var profileImage: UIImage?
Whenever a plant is added to the plantList or a plant is edited, I save the plantList like so:
func savePlants() {
NSKeyedArchiver.archiveRootObject(plantList, toFile:
Plant.ArchiveURL.path)
}
My issue is that when I have more than a few plants in my plantList, then savePlants() takes too long to complete. Not sure how to go forward, would a different persistent storage method like core data be faster? Is storing a UIImage in my Plant class too much data?
NSCoding is an object serialization technique. The choice depends on your cache data. If your cache is a bunch of objects, I’d certainly go for NSCoding, as it’s very simple to work with. NSCoding along with NSKeyedArchiver is a great way to store data which is too big for NSUserDefaults but too small and non-numerous for CoreData.
On the other hand, if you want to optimise this code, I'll suggest you to rather save the image name instead of saving the whole image for persistence. Save the image in app's document's directory(of course with the same name) and get fetch it when you are done finishing decoding using the name you decoded. I think it might give you a nice kick in the performance.
I'm making a game that has skills which each have their own level and experience depending on how much they player has trained them. It also has things like money and items stored in a bank.
Where should I save these items so that you can access it from any view controller and so that it will save when you close and open the game?
I've decided to try and use the UserDefualts but I'm not sure what I'm doing wrong.
Could someone explain if I wanted to have a variable called coins and have a label that displayed these coins starting from 0 and every time a button is clicked the coins go up by 1. Then also be able to close the game or witch views and have the coins stay the same as before it was closed?
Firstly you should define if the game data is sensitive.
If it is not sensitive, try to use NSUserDefaults, it is a fast way to provide primitive storage for the data that you have described.
If it is and data should be protected, try to use more sophisticated ways like CoreData and Realm. They have a bunch of security wrappers that will not give so easy way to manage saved data.
But you also could use cool features such as iCloud, this option will give you the possibility to store and sync game data between platforms and be quite secure.
You can create a singleton and store your data there.
class GameState {
static let shared = GameState()
var items: [Items]
var balance: Int
var level: Int
var xp: Double
func save() {
// Save to CoreData
}
}
To access it from any other class:
GameState.shared.xp = 30
GameState.shared.save()
You would of course store the data in a database, but the singleton gives you a convenient access.
I'm using SharkORM to create a SQLite database but I have the following question.
How can I encrypt and ignore a property in sharkORM?
class Example: SRKObject {
dynamic var birthdate : NSDate?
dynamic var age : NSNumber?
}
I'm trying to calculate the age from the birthdate, and I don't want to have a column in the table for the age.
Also, my data should be secure so I want to encrypt the birthdate, how can this be implemented?
Thanks for your support.
It appears that I might be wrong about ignoreEntities - that's not what you need. It appears that their documentation is not updated to reflect this but what you actually need is ignoredProperties :)
The actual Swift code you need to ignore a property on an object would look like this - I am using an example Person object to illustrate the code:
class Person: SRKObject {
dynamic var name : String?
dynamic var age : NSNumber?
dynamic var payrollNumber : NSNumber?
override class func ignoredProperties() -> [Any] {
return ["age"]
}
}
Since I have not worked with SharkORM before, I tested the code to make sure that the above does indeed work correctly :)
On the subject of the implementation for ignoredProperties, generally, the unit tests for a project (if they exist) are a good place to start to see how to use a certain method. But strangely enough, SharkORM does not seem to implement any tests to see if ignoredProperties works as it should. Hopefully, somebody from the development team sees this and fixes this oversight :)
With regards to encrypting a specific property, I believe all you need to do is implement encryptedPropertiesForClass. Since the implementation will be similar to the above one for ignoredProperties, I will leave the actual implementation to you :)
From the documentation:
In Objective-C properties need to be implemented using #dynamic, this is to indicate to the ORM that it will control the fetching and setting of these values from the database, and in Swift the property is implemented as var dynamic
So, if you don't want age to be a column in the database, don't mark it as dynamic. Since you want age to be a calculated property, you would have something like:
var age: Int? {
if let birthdate = birthdate {
return // whole years from birthdate to today
}
return nil
}
I'm trying to build up a Realm of a bunch of data. This wouldn't be a problem but I've hit a wall - a gap in experience shall we say.
Creating one record is fine. However, one of the fields of that record is an array (<List>) of records from another table. Now my 2 questions are:
Does Realm support that? A list or array of Objects as one of the fields for a record... Answering no here will leed me on to an answer of my question - I will simply need to make an array of "primary keys" and query with those when I need to. If the answer is yes, proceed to question 2.
How would I go about creating those lists, bearing in mind that those tables might be created at a fraction of a second later than the current one, meaning those records don't yet exist and therefore can't be added to the list...
Example:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
var films = List<Film>()
}
See how all the film objects need to be created first before the character objects? Otherwise I could try add a film which does not yet exist and then it all crashes and burns :( Reason I want to try find a better way is, I'm dealing with 10 tables, a few hundred records and variable connection speeds. It would be too long to wait for each data retrieval to finish before the next one starts. AND since they are all suffering from the same problem (inter-connections), regardless of which I start with, it won't work...
Thank you :)
As discussed, for the object that haven't been created, you should create an empty object with only the primary key, then re-fetch and add value after the other network request called
For Many-to-many relationship, you can use Realm's Inverse Relationships to create linking between these objects like:
class Character: baseRLMObject {
var name: String!
var films = LinkingObjects(fromType: Film.self, property: "characters")
}
Like it's being discussed in the comments, you should be able to use Realm's inverse relationships feature to automate most of this process:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
let films = LinkingObjects(fromType: Film.self, property: "characters")
}
When calling character.films, a List will be returned of all of the Film objects whose characters property contains that object.
This is a lot easier and error-free than trying to maintain two separate relational lists between object types.
I'm trying to make a weather app in Swift that will save the cities I add to Core Data, each city contain a weather object that is also saved to Core Data and other various variables.
But I soon figured out, using Core Data NSManagedObjects subclasses outside a Core Data context is close to impossible (dealing with NSNumber and similar, no custom init, forced to save them somewhere, what if I stop using Core Data tomorrow, ...).
So what's the best practice to keep using Core Data but also use models outside of its context?
My solution right now is to create a Class for each Model, so :
class City
{
var country: String?
var name: String?
// ...
}
Is the corresponding class of :
class CD_City
{
#NSManaged var country: String?
#NSManaged var name: String?
// ...
}
So I can use City anywhere and anyhow I want. But I need a func to turn a City into CD_City and opposite. So I'm really not sure I'm doing it the best way.
Also what would you recommend as a conversion method ?
(FYI I'm using MagicalRecord as a Core Data helper)
TL;DR - Don't do that or things will break.
There used to be various hacks for getting it to sort of work, but they all rely on undocumented behavior in CoreData. I would never use anything like that in code I wanted to show another human being, much less ship to customers. CoreData needs to insert proxy objects that hook into property change events on your model objects, and the only way it can reliably do that and track the original data values were is if it is responsible for creating those entities in the first place; That also makes the faulting & uniquing system work. Don't think of Core Data as an ORM, it really is an object graph management framework, and as such it is designed to be used a certain way with no easy solution to side step it safely.
If you don't want to save an NSManagedObject or a subclass of it, then you can create it with
init(entity entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
and pass nil for insertIntoManagedObjectContext this will create you an instance but it will be not be saved to the MOC.
In case you have to save it to the MOC later, you can use NSMangedObjectContext's
func insertObject(_ object: NSManagedObject)