I have the following requirement: create and populate a SQLite database with data from a .xml file, this file can have a different structure any time so I cannot create the NSManagedObjectModel with Xcode, it must be at runtime. I've successfully created a NSManagedObjectModel programmatically, created the persistent store and populated the sqlite file with data from the .xml . However, the next time I use the app the persistent store is not compatible with the NSManagedObjectModel (I don't want to recreate the Model every time I run the app, just the first time). Is there any way to save the model I created programmatically and load it the next time it is needed? All I can see in the examples are models being loaded from the NSBundle.
Is there any way to save the model I created programmatically and load it the next time it is needed?
Yes. NSManagedObjectModel conforms to NSCoding, which means that you can easily convert it to/from NSData, and saving and reading NSData is easy.
To save a model:
NSString *modelPath = // path where you want to save
NSData *modelData = [NSKeyedArchiver archivedDataWithRootObject:self.managedObjectModel];
[modelData writeToFile:modelPath atomically:YES];
To read a saved model:
if ([[NSFileManager defaultManager] fileExistsAtPath:modelPath]) {
NSData *savedModelData = [NSData dataWithContentsOfFile:modelPath];
NSManagedObjectModel *savedModel = [NSKeyedUnarchiver unarchiveObjectWithData:savedModelData];
}
I'm not sure if you are saying that the data in the xml file is changing each time or what. It sounds like you are referring to the data, not the data model. I can't answer specifically, but I would take the approach as follows.
If the data in the xml file is structured the same or close to the same each time, I would create a data model to match that.
Then I would write some sort of parser class that would read the xml and parse it into the Core Data data store according to you "ManagedObjectModel" or data model.
I have seen the error you are talking about when you change the datastore outside of Core Data. You need to let Core Data handle all the reading and writing to the data store or else Core Data will tell you basically that "Your Persistent Store was created or altered by something other than your ManagedObjectModel". I think this is what is happening.
I know I am not using the terminology exactly as Core Data puts it, but Core Data is confusing and I'm trying to convey the message and understanding.
I would also look in to using MagicalRecord. It Drastically makes Core Data easier to work with and there is a great tutorial on www.raywenderlich.com which you can find Here
I really hope this helps you out some. If not, please post some sample code or maybe an example of that xml you are referring to.
Good Luck
Related
I'm work in my application with Core data and want save my zip file to core data entity. Please, tell me is possible and how is possible do
As vadian suggest its a bad practice to store file to core data, if you still wants to save it.
Convert the zip file to data and store the data to Core Data.
[NSData dataWithContentsOfFile: <file_path>] //Objective C
Data.init(contentsOf: <file_path>) //Swift
And Core Data supports the Data attribute, so you will be able to save the data.
My question is related to migration. I cannot do a lightweight migration as there are a lot of changes with attribute types and new relationships. I don't have time for a heavy weight migration since the code is not mine and needs faster delivery.
The workaround, which could work is when the app is upgraded, app should remove the old data and data model as the data is of no use and can be downloaded from the server again. On the app did finish launching, get the .db URL and just remove it and recreate it for the very first time after the upgrade?
After some research, all the methods are pointed to light weight migration. If there is a better way please assist.
-(void) removeCoreDataAndReset{
NSError *error;
NSPersistentStoreCoordinator *storeCoordinator = storeCordinator;
for (NSPersistentStore *store in storeCoordinator.persistentStores) {
[storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error];
}
// Initialise managedobjectcontext , Store Coordinator etc
}
Reinitialise all after this method as you do in statrt
To remove the persistent store, you need to remove:
The actual persistent store file. This is located wherever you put it. You tell Core Data where it is when you call addPersistentStoreWithType:configuration:URL:options:error:, so if you're not sure, check there.
The journal files. These will have the same name as the persistent store file, but with -wal and -shm added to the end. This is very important, because in most cases nearly all of the existing data is in these files.
You can remove files with methods on NSFileManager. If you do this, do it before accessing Core Data in any way, i.e. before creating any Core Data objects of any kind.
I need to cache json data from API in swift.
So I researched a Lot & get to this Post.
I tried to implement the Option 1 in my App. But the Custom manager always returned nil. I don't know why?
After that I got AwesomeCache. It says that it an do Awesome API Caching.
But I don't know how to implement this?
I referred this Issue. Still I can't figure it Out.
This is how my Current implementation Looks without Cache:
Alamofire.request(.GET, "http://api.androidhive.info/volley/person_array.json")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let catCount = json.count
for index in 0...catCount-1 {
let name = json[index]["name"].string
println(name)
}
Please suggest me the Best way to Cache JSON from API ?
Thanks in Advance!
UPDATE
These are my requirements
Fetch the JSON from the API & Parse the JSON data. These can be done with the help of Alamofire & SwiftyJSON
I will populate the parsed data in the Table View. It works when the user is in Online.
But I want to show the data in the Table when the user is in offline too.
So I need to save the Parsed data or the JSON data in my cache & I need to refresh or expire the cache within a week or days.
I don't prefer to store the JSON in my disk because it will be updated.
Please suggest me the Best way to achieve this...
You have many tools already at your disposal.
NSURLCache
All your requests are already stored in the NSURLCache in the NSURLSessionConfiguration on the NSURLSession stored inside the sharedInstance of the Alamofire Manager. Those stored requests already follow all the caching policy rules provided by the servers you are hitting. You can control the caching behavior by setting the requestCachePolicy on your own custom NSURLSessionConfiguration. I'd also suggest you read through this awesome NSHipster article that walks you through the ins and outs of NSURLCache and how to control it.
Creating custom Manager objects is covered in the current Alamofire docs.
Downloading JSON to Disk
You can also download the JSON directly to disk using Alamofire.download instead of using Alamofire.request. This will download the payload to a fileURL that you provide in the destination closure. This would give you full control over the caching of the file after that point. You would need to create your own caching policy around these files afterwards if you wanted to follow the caching header rules provided by the server.
Populating Table View
Once you have your data downloaded to disk, you need to load it into an NSData blob and parse it into JSON to populate your table view. This should be pretty straight forward. You need the destination NSURL that you specified to Alamofire when you started your download. Then load the file data into an NSData blob. Finally, use NSJSONSerialization to convert the NSData object into a JSON AnyObject which can be parsed into model objects to populate your table view.
Obviously you don't "have" to parse the JSON into model objects, but this helps protect your table view from malformed JSON data.
Storing JSON for Offline Usage
If you stick with this approach, you'll need to track your cache expiration dates in something like CoreData or SQLite. You can do this by either caching the paths to the JSON files on disk, or store the model objects directly in CoreData or SQLite. This could get fairly complicated and I would not recommend this approach unless you absolutely don't want to cache your model objects.
Offline Usage
Generally, if you need to cache data for offline usage, you want to store your model objects in something like CoreData. You would use the Alamofire request method coupled with a responseJSON serializer to parse the data into JSON. Then you would convert the JSON into model objects. From there, you'd save your model objects in CoreData, then finally populate your table view with the model objects.
The nice thing about this approach is that you have all your model objects cached in the case that your table view is accessed when the device is offline. Coupling this design with queries to your NSURLCache to see if your request is cached let's you avoid unnecessary server calls and parsing logic when you already have your model objects generated.
Given the updates to your original question, I would recommend this approach.
You can use this cache open source. It cache data on disk and memory. Can cache many swift type, and custom class which inherit NSObject and conform NSCoding protocol.
https://github.com/huynguyencong/DataCache
To implement:
First, it use NSCache for mem cache. NSCache use like a dictionary.
Second, save cache to disk, use NSFileManager methods.
I'm using Rest Kit with Core Data, one of the Core Data entities has an attribute 'image' that has a binary type.
I'm still in mockup stage so the image is populated with this code:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lorempixel.com/60/60/people"]]];
entry.image = UIImagePNGRepresentation(image);
Another tab has a collection view that uses fetchedResultsController.
After creating a new entity, if I only save the context the image works fine.
But if I push the entity to the web server using 'postObject:' the image is corrupted when it comes back from the server. I've confirmed the server receives the same string representation of the image "<2f396a2f 34414151 536b5a4a 52674142 ... 6a6e502f 32513d3d>" and stores it directly into a MySQL column of type long blob and at all points the string representation is the same.
But when the collection view is populated using a server call via RestKit the entities image is invalid. I'm think the issue is the data is being converted into the data representation of the description of the data.
Does anyone have a working example with images. The only thing I can think of is that I need to add a custom transformation, but the documentation and examples are lacking as far as how to actually implement one.
RestKit is storing the plain NSData for the image in Core Data - it has no idea what else you might want to do with it. Generally you don't want to manage images directly in Core Data or using RestKit.
Generally, store the path of the image in Core Data and the file on disk. Download them asynchronously (from the URL's which would also be in Core Data).
For uploading, you could make RestKit upload the data, but you probably actually want to file upload or convert to base64. You will need to write some code for this (which you could have RestKit pick up by using the key of the method name that returns the appropriate data). A similar process will work for mapping the data in.
RestKit data transformers are hard to make work in this situation as you are converting between data and strings and they are too general to be able to intercept accurately.
Purpose: I have to create entities from files.
So entities represent my data model in CoreData and files have all information for this entities.
All files I get from Internet. For this I use AFNetworking framework.
How I get files (algorithm):
Request plist file. Plist file has values to other urls that I have to download.
When plist was downloaded to my Documents directory on device. I parse it.
When I parse plist I grab url from each item from NSDictionary that represent this plist.
Then I request zip files from this urls.
After zip files were downloaded I unzip them and go to the next step.
Parse unzipped files and create data model.
It is my problem. I have version of file that stored locally and that stored on the server and when version on the server changed I need to reload my data model with actual data. The bad way it is load all data from server again next delete all entities in storage and make new entities from new data. But it is not professional way at first and second it is an additional burden on the traffic, because if I have just one entity that I need to reload why I have to reload other entities that are in the actual state. So maybe someone knows best practice with this question. Of course I can create my solution and it will work, but I want to see how people solve this problem and figure out with the differences in my solution and in the other solutions also.
This is trivial. You simply keep an attribute with the time stamp of the last update and just request the changed and new entities from your server. Then you insert, update or delete as appropriate.
It sounds like you are talking about find-or-create. Depending on the size of the data set and your performance requirements you can do this a couple of ways:
The first way is to fetch your existing Core Data objects and store them in an a dictionary with a unique attribute of the entity serving as the key. Then when you download the new data you can take this key for each parsed object and check your dictionary to find (and optionally update) any existing object, or create a new one:
NSArray *fetchedObjects = //Fetch the existing objects;
NSMutableDictionary *existingObjects = [NSMutableDictionary dictionary];
for (MyClass *object in fetchedObjects)
[existingObjects setObject:object forKey:object.uniqueKey];
//Now iterate through the new data (using JSON as example here)
NSDictionary *downloadedItems = //Download data and serialise into JSON
for (NSDictionary *item in downloadedItems){
NSString *uniqueValue = [item valueForKey:#"uniqueKey"];
MyClass *object = [existingObjects objectForKey:uniqueValue];
if (object==nil){
object = //Insert new MyClass entity into MOC
object.uniqueKey = uniqueValue;
//Set any other values you only need to set once
}
//Set any values you may need to update every time
//This would be where to check a modified date attribute
}
The second way is more sophisticated, and involves less memory overhead. It's described in the Efficiently Importing Data section of the Core Data Programming Guide.
The guide gives a good start but doesn't offer a complete solution; I attempted my own in an answer here: Basic array comparison algorithm