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.
Related
Let's say I have a model like below
class Employee: Codable {
var id: String?
var personalDetails: Person?
}
class Person: Codable {
var firstName:String?
var lastName: String?
}
Now I have to maintain an Entity(say DBEmployee) inside CoreData which will have the exact same properties
Now, is there a way to NOT create a separate NSManaged subclass(DBEmployee) for that entity which will have the same properties(as Employee) and have that entity extract it's properties as per the properties of Employee
My main problem is when I create my CoreData operations for fetch, it returns me DBEmployee which I then have to convert to Employee which has exactly the same properties (because the method that uses this fetch accepts Employee as the parameter and not DBEmployee)
You can get values back as either a NSManaged object / subclass or a dictionary of [String:Any] depending on the fetch result type. However, neither of those is a POSO (plain old Swift object.)
Since they both have the exact same properties, can you can change the method / API to take a protocol instead?
It really comes down to if the API really needs a Codable model (because it’s going to serialize it, for example) or if you can change the method (or possibly add to it)
Say I have 2 NSManagedObjects in CoreData.
class House: NSManagedObject {}
class Location: NSManagedObject {}
I also have data model structs like this:
struct HouseModel {
var objectID: NSManagedObjectID
...
}
sruct LocationModel {
var objectID: NSManagedObjectID
...
}
For each loaded managedObject I basically use its attributes to initialize a new model struct to use for the UI and stuff (mainly collection views)
I have to have the NSManagedObjectID attribute in the structs in order to be able to make changes to the managedObject that struct belongs to. (I learned that I should use the mainViewContext only for reading while using something like persistentContainer.performBackgroundTask for writing. Thus, I need the NSManagedObjectID to load the objects on a background queue)
That's working but there is a problem with this approach:
I can't initialize one of these data models without a managed object. That's annoying when I want to create dummy data for UI testing or unit testing.
I know one solution: Create a Dummy managedObject with exactly one instance and use its objectID for stuff like that. But I don't really like this. Is there a better / more convenient way?
I mean, I would love to entirely remove the objectID attribute to keep CoreData separate from these model structs. But I don't see a way to do this. I need the connection.
For passing NSManagedObjects to a detail view for editing, it is often useful to do that on a new main queue managed object context, which simplifies your UI access and allows you to throw away the context if the user cancels changes.
But that's not what you asked.
Your problem is that you want to identify a managed object, but not use NSManagedObjectID. For this, you can use a URL property instead. NSManagedObjectID has a uriRepresentation() that returns a URL, and NSPersistentStoreCoordinator can convert a URL back into a managed object ID using managedObjectID(forURIRepresentation:). So you can store any old URL in the struct for testing purposes, and still be securely referring to managed objects in your app logic.
I've having difficulty saving an NSManagedObject in userDefaults and I'd like to know a) should I be trying to do this or is this not an appropriate approach or b) if it is an appropriate approach, how can I get it to work?
I'm writing my app in Swift 2.3 and it has a few user default options, one of which is for a default "lift" (as in weightlifting, e.g. 'bench press', 'clean and jerk', 'incline bench press'). I'm actually converting them from an enum to a Core Data entity because every lift event that the user will be able to keep track of will be one of the available lifts types (for which I'll establish the appropriate relationship).
Here's the extension with the properties:
extension Lift {
#NSManaged var liftName: String
#NSManaged var type: NSSet
}
and the Lift entity with the things Xcode is complaining about:
class Lift: NSManagedObject, NSCoding {
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(liftName, forKey: "liftName")
} // Super.init isn't called on all paths before returning from initializer
required init(coder aDecoder: NSCoder) {
// Initialization of immutable variable was never used, etc...
let liftName = aDecoder.decodeObjectForKey("liftName") as! String
}
}
I've dealt with these types of errors before so my real concern is whether or not I'm headed down the wrong path.
I've read numerous threads tonight which have taught me that I'll need to encode an object (but not specifically an NSManagedObject) to save it then unencoded it when retrieving it and that my class must conform to NSCoding and what that protocol requires. But then I've seen threads that say NSManagedObjects should NOT be stored in userDefaults, but I don't know if that's true.
I've spent a few hours on this so before I go further, can/should this be done?
No, you should not store an NSManagedObject in NSUserDefaults. Core Data is an object persistence technology, so it doesn't make sense to try and persist an NSManagedObject in some other format.
There are few alternatives you can use:
You could simply store the name of the lift in your user defaults and then query for this to get the object back from Core Data. This may not work for you if lift names aren't unique
You can add an identifier attribute to your Lift entity and store something like a UUID string in that attribute; You can then store the same string in UserDefaults. This will ensure one object is selected uniquely.
You can add a new boolean attribute to your Lift entity default and again use a query to retrieve it; You would need to ensure you only set the value to true on one lift at a time.
You can use managedObject.objectId.uriRepresentation to get a URL that you can store as a string and then use to retrieve the object later. This is probably the most complex solution and I would suggest you try one of the other options.
No, You cannot do this. You cannot save Core Data objects in User Defaults. It can only save in its DB.
This two are totally two different things. NSUserDefault stores the light pieces of data where NSManagedObject stores the light or heavy amount of data and is very fast than NSUserDefault for storing and retrieving purposes.
NSManagedObject -> NSManagedObject link to coredata.
You should it to store a large list of elements. As far your last question, there is nothing preventing you from using both Core Data and a backend to store your data. In fact, there are frameworks out there to facilitate exactly this.
NSUserDefaults -> NSUserDefaults is a class that allows simple storage of different data types. It is ideal for small bits of information you need to persist between app launches or device restarts. NSUserDefaults is not sufficient and reliable to store and query the huge amount of data. It's suggestable if you'll have a backend (database on the server) to store events and their invitees to persist consistency of user's information (if user logged in back to your app from other app supportive device then he'll get all information he stored).
NSUserDefaults supports the following data types:
NSString, NSNumber, NSDate, NSArray, NSDictionary and NSData
Hope it will help you.
Storable Types in NSUserDefaults.
The NSUserDefaults class acts very much like something called a Property List (aka plist). It may be just a fancy interface for a plist, or it may be more, I’m not entirely sure. Nonetheless, plists are limited in what kind of objects they can store. The six types plists can store are:
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary
So,you need to use in NSKeyedArchiver.
let ArchvieArr = NSMutableArray()
ArchvieArr.addObject(NSKeyedArchiver.archivedDataWithRootObject(LiftObj)).
NSUserDefaults.standardUserDefaults().setObject(ArchvieArr, forKey: "savedArray")
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.
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)