Showing posts with label core-data. Show all posts
Showing posts with label core-data. Show all posts

Tuesday, September 25, 2018

performBackgroundTask not saving when app in background Swift

Leave a Comment

I'm using Xcode 9 and Swift 4. I start a download of multiple JSON files when the application is in the foreground. The application then parses these files and saves them to CoreData. This works well when the application is in the foreground. However, if the application is in the background, the files still download correctly, but the data is not parsed and saved to CoreData. It's only when the user returns to the foreground that the parsing and saving of data continues.

I have Background Modes turned on - Background Fetch and Remote notifications.

I have around 10 functions that are similar to the one below in which it processes the JSON files concurrently:

func populateStuff(json: JSONDictionary) -> Void {     let results = json["result"] as! JSONDictionary     let stuffData = results["Stuff"] as! [JSONDictionary]      let persistentContainer = getPersistentContainer()      persistentContainer.performBackgroundTask { (context) in         for stuff in stuffData {             let newStuff = Stuff(context: context)             newStuff.stuffCode = stuff["Code"] as? String             newStuff.stuffDescription = stuff["Description"] as? String              do {                 try context.save()             } catch {                 fatalError("Failure to save context: \(error)")             }         }     } }  func getPersistentContainer() -> NSPersistentContainer {     let persistentContainer = NSPersistentContainer(name: "MyProjectName")     persistentContainer.loadPersistentStores { (_, error) in         if let error = error {             fatalError("Failed to load core data stack: \(error.localizedDescription)")         }     }     persistentContainer.viewContext.automaticallyMergesChangesFromParent = true     persistentContainer.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump     return persistentContainer } 

Can anyone advise me on why this might happen and how to over come this?

TIA

1 Answers

Answers 1

Use the beginBackgroundTaskWithName:expirationHandler: method:

func populateStuff(json: JSONDictionary) -> Void {      // Perform the task on a background queue.     DispatchQueue.global().async {          // Request the task assertion and save the ID.         self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "Finish Network Tasks") {          // End the task if time expires.         UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)         self.backgroundTaskID = UIBackgroundTaskInvalid     }      // Parse the json files      let results = json["result"] as! JSONDictionary     let stuffData = results["Stuff"] as! [JSONDictionary]      let persistentContainer = getPersistentContainer()      persistentContainer.performBackgroundTask { (context) in         for stuff in stuffData {             let newStuff = Stuff(context: context)             newStuff.stuffCode = stuff["Code"] as? String             newStuff.stuffDescription = stuff["Description"] as? String              do {                 try context.save()             } catch {                 fatalError("Failure to save context: \(error)")             }         }     }      // End the task assertion.     UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)     self.backgroundTaskID = UIBackgroundTaskInvalid } 

Calling this method gives you extra time to perform important tasks. Notice the use of endBackgroundTask: method right after the task is done. It lets the system know that you are done. If you do not end your tasks in a timely manner, the system terminates your app.

Read More

Friday, June 29, 2018

Inter-App Data Migration (migrating data to new app version)

Leave a Comment

I currently have a Swift iOS app on Apple's App Store. I have many users and I would like to make a new version and help current users migrate to the new version. FYI: the new version is an Ionic app.

Data-wise, my app is using Core Data without any iCloud or sync support. It contains JSON data and also multiple images. So I'd need to bundle the current data and find a way of bringing it to the new ionic app version.

Basically my question is: Is there a way of writing in the app's documents directory and let the new version grab that file to import its data? Is there a way of letting both apps transmit data other than AirDrop or Custom URLs?

I don't want to upload the data remotely, I'd like to do this all locally on the device and also seamlessly so the user don't have to manually do anything.

Suggestions are welcome, thanks!

2 Answers

Answers 1

I would have just left a comment but I am unable to do so.

I was able to see that after loading a native iOS application then a Ionic project with the same bundle structure that the data within Library/Application Support/DisplayName.sqlite was still there and the data within the database still intact. (Note: this is deploying using Xcode not through the App Store)

You can see this using Xcode -> Window -> Devices and Simulators -> Devices tab -> Click on your app -> settings cog -> Download container -> after saving Show package contents

I was unable to use the Ionic SQLite native plugin to open the database for some reason. That is as far as I could get. I think it might have something to do with the space of the Application Support folder.

Answers 2

You can do the transition without using AirDrop or Custom Url. The idea is based on how ionic works. Much of the ionic functionality depends upon the plugins developed by community like working with hardware features.

Dealing with device specific features are done in native codes then JS wrapper classes are created for making a bridge between your code and native code.

I would suggest you to write native code which will access the data and files from CoreData and then use the cordova plugin tech to setup communication between the native code and the ionic code. here is a good post on Creating Custom Plugin for ionic and a sample github project

Read More

Monday, November 20, 2017

Issue Google maps API on iOS 11 with Xcode 9

Leave a Comment

I'm trying to set a map in a view in my app and I got this problem:

CoreData: annotation: Failed to load optimized model at path '/var/containers/Bundle/Application/35C61A40-48B9-40E0-A6F9-AB7492A15009/simply-convertor.app/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.omo' CoreData: annotation: Failed to load optimized model at path '/var/containers/Bundle/Application/35C61A40-48B9-40E0-A6F9-AB7492A15009/simply-convertor.app/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.omo' CoreData: annotation: Failed to load optimized model at path '/var/containers/Bundle/Application/35C61A40-48B9-40E0-A6F9-AB7492A15009/simply-convertor.app/GoogleMaps.bundle/GMSCacheStorage.momd/Storage.omo'

I add an empty view to my ViewContoroller and change it's type to GMSMapView. And in viewDidLoad method I create a new map from location and init my main mapView:

override func viewDidLoad() {         super.viewDidLoad()         placesClient = GMSPlacesClient.shared()         locationManager.delegate = self         locationManager.desiredAccuracy = kCLLocationAccuracyBest         locationManager.requestWhenInUseAuthorization()         locationManager.startMonitoringSignificantLocationChanges()          locationAuthStatus()         print(location.coordinate.latitude, location.coordinate.longitude)         let camera = GMSCameraPosition.camera(withLatitude: 31.9650070083707, longitude: 34.7899029677496, zoom: 6.0)         let map = GMSMapView.map(withFrame: CGRect.zero, camera: camera)         self.mapView = map          // Creates a marker in the center of the map.         let marker = GMSMarker()         marker.position = CLLocationCoordinate2D(latitude: 31.9650070083707, longitude: 34.7899029677496)         marker.title = "Home"         marker.snippet = "Home"         marker.map = mapView     } 

what is the problem?

1 Answers

Answers 1

I find my problem.. it appears I misunderstood the initialliztion of a GMSMapView and not initialize it correct..

From other post the CoreData error that I mention is a problem of Google's API and it doesn't affect the app.

Read More

Sunday, September 10, 2017

Periodic iCloud backup of SQLite database

Leave a Comment

Let me get this out of the way right now: yes, it was almost certainly a mistake to not use Core Data. However, I was new to iOS development when I made these decisions, and I had no idea I'd be hamstrung like this. Moreover, the app is intended to also run on Android (eventually), so I avoided platform-specific APIs wherever possible.

I have an iOS app that stores data in a local SQLite database file. The data stored in the file is provided by the user, so it's important that it be kept safe. I had plans to "do this later", and later is now here. I am quickly coming to the realization that it won't be as straightforward as I had hoped...

I now understand that it won't be possible to seamlessly synchronize data across devices, and I'm willing to accept that limitation until I manage to migrate to Core Data. However, in the meantime I'd at least like the SQLite database to be backed up periodically so users can feel safe using the app on a single device. I was thinking I would do this:

  • periodically (e.g. once a week) copy the SQLite file from local storage into cloud storage, thus ensuring it is backed up
  • when the app starts, if the local store is missing or corrupted but the file exists in the cloud storage, ask the user if they would like to copy it over

The biggest problem with this approach is that the user could run the app on multiple devices and therefore the data stored in iCloud could be from any one of those devices, but only one. To combat that, I thought I could just use a per-device, unique name for the file in cloud storage. I would generate this using UIDevice.identifierForVendor.

So my startup logic would be:

  1. Determine the unique name for the cloud file.
  2. Is the local file missing or corrupted, and if so, does the cloud file exist?

    2.1. Ask the user if they would like to restore from the cloud file. Make it really hard for them to say no because doing so will lose all their data.

    2.2. If they say yes, copy the cloud file to the local file storage.

  3. Open the local database file.

And running in the background I would occasionally copy the database file from local to cloud storage.

I would like to know whether this a sensible approach until I do Core Data integration. Also, are there any hidden "gotchas" that I'm perhaps missing?

UPDATE: as @TomHarrington pointed out in a comment, it turns out my database file is already sitting in /Documents, which is backed up to iTunes and any iCloud account. So my question morphs into this:

Should I simply ensure my database has a device-specific name so that it is not clobbered by the app running on another device connected to the same iCloud account?

1 Answers

Answers 1

I'm going to answer my question, since I ended up going down this path and finding a MASSIVE blocker. There is a bug in the UIDevice.identifierForVendor API that causes it to regenerate every time a new version of the app is installed! See here. This of course rules out using it as a device identifier. sigh

I think I'm SOL with that approach. Instead, I might generate a GUID on first execution and use that as my identifier. Problem is, I need to store that somewhere that isn't backed up to iCloud.

Ugh, I may just give up here and say my app can't be run on multiple devices until Core Data integration is done.

Read More

Monday, June 19, 2017

How to purge an entity and after add new records with magical record

Leave a Comment

When I request new data I want to delete all the records before adding news. Sometimes there is no changes between the old datas recorded and the new datas.

This is what I tried but my new record are never saved (always 0 records saved)

When I request data:

Autorisation* aut = [Autorisation MR_createEntity]; // setter method 

When I want to save:

+(void)saveAutorisationList:(NSMutableArray*)autorisationList{     NSManagedObjectContext* localContext = [NSManagedObjectContext MR_defaultContext];     for (Autorisation* aut in [self getAutorisationList]) {       [aut MR_deleteEntityInContext:localContext];  // method that return all Autorisation     }     [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * error) {         for (Autorisation* aut in autorisationList) {             [aut MR_inContext:localContext];         }         [localContext MR_saveToPersistentStoreWithCompletion:nil];     }]; }  +(NSMutableArray*)getAutorisationList {     NSManagedObjectContext* localContext = [NSManagedObjectContext MR_defaultContext];     return [[Autorisation MR_findAllInContext:localContext] mutableCopy]; } 

1 Answers

Answers 1

What is going on here is that you're deleting all objects, including those which you want to save. Here is step by step what's going on:

+(void)saveAutorisationList:(NSMutableArray*)autorisationList {     // as it seems, here autorisationList is a list of new objects you want to save.      NSManagedObjectContext* localContext = [NSManagedObjectContext MR_defaultContext]; 

Wrong behavior starts here:

    for (Autorisation* aut in [self getAutorisationList]) { 

Here getAutorisationList fetched all objects, currently existing in localContext, old + new ones.

      [aut MR_deleteEntityInContext:localContext];         // here you deleted each Autorisation object currently existing, including those you want to save     }     ... } 

Instead, you should find diffs between what you received and what existed before, and delete only those objects which were not received with the update.

E.g. imagine you had set of objects OldSet = {auth1, auth2, auth3}, and with update you received objects NewSet = {auth2, auth3, auth4}. Diffs for deletion would be

ToBeDeletedSet = OldSet - NewSet = {auth1} 

In this way you will keep the records which you had, and save new records as well.

Then, your save method will look like this:

+(void)saveAutorisationList:(NSMutableArray*)updatedAutorisationList{     NSManagedObjectContext* localContext = [NSManagedObjectContext MR_defaultContext];     NSMutableArray *oldAutorisationList = [self getAutorisationList];     [oldAutorisationList removeObjectsInArray: updatedAutorisationList];     for (Autorisation* aut in oldAutorisationList) {       [aut MR_deleteEntityInContext:localContext];  // method that return all Autorisation     }     [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError * error) {         for (Autorisation* aut in autorisationList) {             [aut MR_inContext:localContext];         }         [localContext MR_saveToPersistentStoreWithCompletion:nil];     }]; } 
Read More

Monday, April 10, 2017

Core Data classes not generated for test target

Leave a Comment

I use Core Data's automatically generated classes. My project has 3 targets in addition to the test target. For each target, the Core Data classes are properly generated which I verified by inspecting the Derived Data folder. However, classes are not generated for the Test Target despite it being ticked in the Core Data model file. This causes an "undeclared identifier" error when I try to reference one of the Core Data classes in the test target. How can I fix this please?

2 Answers

Answers 1

You do not need extra classes generated for each test target - your import process should import everything, and no files should need to be added to other targets.

Declaring @testable import MyProject should take care of everything.

In Objective C

@import MyProject; 

Answers 2

The Model object autogenerates code which has to be included in your Test project.

Click on your Model object (ends .xcdatamodeld). Select the file tab on the Utility sidebar (The first tab on the right sidebar). Under "Target Membership" add your test target.

Hope that helps. Try to clean the target and build again if it doesn't.

Read More

Tuesday, February 28, 2017

Sort Descriptor based on ordered to-many relationship

Leave a Comment

Description of my core data model:

  • Project and Issues entities
  • Project has an ordered one-to-many relationship to Issues named issues
  • Issue has one-to-one relationship with Project named parentProject

Here is my code to obtain issues:

let fetchRequest = NSFetchRequest(entityName: "Issue")     fetchRequest.predicate = NSPredicate(format: "parentProject CONTAINS[cd] %@", argumentArray: [project])     fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]      let frc = NSFetchedResultsController(         fetchRequest: fetchRequest,         managedObjectContext: dataManager.context,         sectionNameKeyPath: nil,         cacheName: nil)      return arc 

Even though I have all issues from the project object, I prefer to obtain issues using fetched results controller, so they will always be updated and I like the integration with tables. I also like that in navigation controller screen before the projects are displayed using FRC as well.

As you see in the code the Issues are sorted by the name parameter.

However I'd like them to be sorted by the order I keep them in the NSMutableOrderedSet of project.

AFAIK I cannot use NSSortDescriptor with comparator/selector when it's used to Core Data.

Do you know how to do it? Thanks in advance.

3 Answers

Answers 1

If you use the relationship keypath (in your case parentProject) it will sort according to the ordered set. so you can use

fetchRequest.sortDescriptors = [NSSortDescriptor(key: "parentProject.name", ascending: true),NSSortDescriptor(key: "parentProject", ascending: true)] 

This will first sort them by project, then inside each project group it will be sorted by the same order as the ordered set in the relationship. Optionally you can put each issue's project in a different section by using sectionNameKeyPath.

If you don't use parentProject.name (or something like it - like parentProject.lastUpdated) first then they will ONLY be sorted according to the order of orderedSet. In other words it will be all of the first issues, then all of the second issues etc. This is probably not what you want.

Answers 2

This may be a kluge, but anyway. It seems to me your first fetch is a filter.. e.g. show me issues for this project. Maybe you could perform a second fetch, on the selected issues, and this time not filtering them, but sorting them?

Regarding CoreData, I have a sorted fetch like this:

  func fetchItem() {         print(#function)          let sortDescriptor: NSSortDescriptor!         let defaults: UserDefaults = UserDefaults.standard         if defaults.integer(forKey: "sequenceSwitchValue") == 0 {             sortDescriptor = NSSortDescriptor(key: "itemName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))         } else {             sortDescriptor = NSSortDescriptor(key: "itemName", ascending: false, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))         }          let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext         let request = NSFetchRequest<NSFetchRequestResult>(entityName: itemEntity)         request.sortDescriptors = [sortDescriptor]         let predicate: NSPredicate = NSPredicate(format: "itemName == %@", incomingItemName)         request.predicate = predicate           do {             items = try context.fetch(request) as! [ItemEntity]         } catch let error as NSError {             print("Error While Fetching Data From DB: \(error.userInfo)")         }          if items.count > 0 {             let currentItem = items[0] 

You may be able to work a relationship value into the predicate..

Answers 3

The simplest way to keep order is to add a property for the "issue" entity. Something called position of type Int. And use sort Descriptor that would sort entities by this position property

Read More

Wednesday, February 15, 2017

Why might Core Data fail to merge data from private context to main context?

Leave a Comment

I have an application using Core Data with the following, fairly standard, managed object context hierarchy:

Persistent Store Coordinator      ↳ Save Context (Private Queue Concurrency Type)          ↳ Main Context (Main Queue Concurrency Type)         ↳ Private Context (Private Queue Concurrency Type) 

The merge policy for all managed object contexts is set to NSMergeByPropertyObjectTrumpMergePolicy

I am observing NSManagedObjectContextDidSaveNotification which will invoke the following function when the Private Context is saved and merge changes to the Main Context:

func contextDidSaveNotificationHandler(notification: NSNotification) {      if let savedContext = notification.object as? NSManagedObjectContext {         if savedContext == privateObjectContext {              mainObjectContext.performBlock({                  if let updatedObjects = notification.userInfo![NSUpdatedObjectsKey] as? Set<NSManagedObject> {                     //                     // fire faults on the updated objects                     //                     for obj in updatedObjects {                         mainObjectContext.objectWithID(obj.objectID).willAccessValueForKey(nil)                     }                 }                  mainObjectContext.mergeChangesFromContextDidSaveNotification(notification)             })         }     } } 

This is working most of the time but sometimes I am finding that changes to existing objects in the Private Context are not being merged into the Main Context. I can't figure out why -- the private context save is successful; the NSManagedObjectContextDidSaveNotification notification is being sent; the notification handler is being invoked; notification.userInfo?[NSUpdatedObjectsKey] contains the correctly updated objects; but at the end, the main context is not synchronized with the private context. (ie: the managed objects in the main context are not in sync with the values contained in notification.userInfo?[NSUpdatedObjectsKey]) If I kill the app and relaunch it, the contexts become synchronized again (after loading objects from the persistent store).

I have -com.apple.CoreData.ConcurrencyDebug 1 enabled in my launch arguments, and all Core Data multithreading rules are being followed. I can't see anything overtly wrong with my managed object context hierarchy or the merging function. What could possibly be causing this?

3 Answers

Answers 1

You can't merge sibling context. You have to merge from the parent down. In other words change

 if savedContext == privateObjectContext { 

to

 if savedContext == savingObjectContext { 

where the savingObjectContext is the parent context of the main context.

Answers 2

You could always set your mainQueueContext as your privateQueueContext's parent:

privateObjectContext.parent = mainObjectContext 

This will merge your saves into your main object context. I used this in a project where I parse JSON, and set core data objects in a privateQueue's perform block, with my mainContext set as the parent. Then I simple save the private, and then access the data from the main thread/main context. Works like a charm. I should add I do not keep the private context in memory, it is created new when I need it.

Answers 3

The problem is with your merge policy. Try changing to to NSErrorMergePolicy and I think you will start to see merge conflicts. At the least this will give you more of an idea of the underlying cause

Read More

Wednesday, January 25, 2017

Can we store NSURLRequest directly into Core Data?

Leave a Comment

I am developing iPad Application in which i need to create Multiple NSURLRequest. When is it fails I need to fire that URL request again.

I have three type (Create School, Create Floor and Create Rooms) of request which contain multiple parameters different create Request.

There is retrial method which can fire when there is internet available with same object which was created on first request.

So I was trying to create three Tables and I was trying to store all parameter with their status.

Is it possible to create Single Table with NSURLRequest irrespective of CREATE REQUEST?

3 Answers

Answers 1

You can probably just save the absoluteString of the NSURLRequest’s URL property. Or do they each have distinct timeouts or cache policies?

Answers 2

Core Data entities correspond to instances of NSManagedObject or a subclass of NSManagedObject, so you can't just save URL requests directly. What you could do is create an entity called something like SavedRequest that has a property representing the URL request-- and maybe some other details about the request (whatever other info you might need-- date, maybe?).

Since NSURLRequest conforms to NSCoding, you would create this property using the Core Data "transformable" type. Core Data would use NSCoding to automatically convert to/from NSData as needed. You would assign an NSURLRequest to the property and read them back, and Core Data would save them as NSData.

Given your description though, Core Data might not make sense. It sounds like you just want to save a list of URL requests and later read it back, and don't need the extra features Core Data would provide. It would be simpler to put your NSURLRequest objects in an array and then save that array to a file or to user defaults. You would convert to/from NSData yourself, but since you can use NSCoding that's easy.

To save the array you'd do something like this, assuming an array called myArray containing URL requests and a path in filePath:

BOOL success = [NSKeyedArchiver archiveRootObject:myArray toFile:filePath]; 

You'd get the array back using

NSArray *savedRequests = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; 

Answers 3

Yes that is possible, at least with Swift. With Swift you can access the Transformable property in CoreData. Using this property it is possible to put any kind of data in Core Data. That even without extra overhead.

See the excellent tutorial http://geekyviney.blogspot.nl/2015/02/transformable-binary-data-properties-in.html

Read More

Thursday, September 15, 2016

How to correct predicate CoreData Fetched property for Chat and Messages weak relationship?

Leave a Comment

I have a simple chat with typically relations chat and messages.

Chat entity have property chatId - type Integer

Message entity have property chatId - type Integer

In chat entity i created a fetched property (messagesFP) with simple (i think) predicate

chatId == $FETCH_SOURCE.chatId 

enter image description here

I have chat with Id = 1, and messages which property chatId = 1.

But messagesFP - return empty array.

If i change predicate to

chatId == 1 

So messagesFP return correct messages.

So, how to write correct predicate to fetch messages for current chat?

1 Answers

Answers 1

If $FETCH_SOURCE points to an NSManagedObjectID on your end, you might want to try using the category below to correct that.

Here is a great reference in the Core Data Programming Guide.

This is one of those convenience methods that we developers must provide on our end. In your project, replace some_moc with your managed object context.

@implementation NSManagedObjectID (FetchSource)  - (id) valueForUndefinedKey:(NSString *)key {      //Attempt to unwrap the underlying object from the moc     NSManagedObject *mocObject = [some_moc objectWithID:self];      return [object valueForKey:key]; }  @end 

I hope that works for you!

Read More

Monday, August 1, 2016

Correct thread to perform delete, insert and fetch from/to Core Data

Leave a Comment

My app connects to a webservice, performs POST from 3 different methods and inserts each of these 3 sets of data to Core Data. Everytime new data is available everything in core data gets deleted and new data is inserted. Each of these 3 methods are displayed in different TableViewControllers, which means more than 3 Fetches everytime each of these TableViewControllers are displayed.

Wrapping things up, we have 3 sets of data that are fetched from a webservice, and then i insert all these into different entities while fetching new data to display in the main TableViewController.

Now i am struggling with the complex relation of threads and multi-context Core Data structure. Where should i perform the insert, delete and fetch to ensure the thread safety of my application?

3 Answers

Answers 1

Do your UI fetches on the main thread from the main context.

For your importing, don't run the session callback on the main thread, run it on any background queue. Create a new private context and set main as its parent. In the session callback use the block interface to import and save the background context and then its parent (again, using the block interface).

Answers 2

In multi-context CoreData, You can have a privateQueueRootMOC and a mainQueueRootMoc, the main MOC is used to fetch data in mainThread, the private MOC is Used to update, insert and delete data in privateMOC thread.

privateMOC.performBlock({ () -> Void in     //try insert, delete, update     ....     try privateMOC.save()     onCompleteBlock() //fetch data in main thread use main MOC }) 

Answers 3

Maybe you should check this article, which compares 3 ways to do background work with Core Data : http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/

Read More

Monday, June 13, 2016

Batch delete request crashes app

Leave a Comment

I have an InMemory Store Coordinator declared like so:

lazy var ramStoreCoordinator: NSPersistentStoreCoordinator = {     // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.     // Create the coordinator and store     let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)     var failureReason = "There was an error creating or loading the application's saved data."     do {         try coordinator.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil)     } catch {         // Report any error we got.         var dict = [String: AnyObject]()         dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"         dict[NSLocalizedFailureReasonErrorKey] = failureReason          dict[NSUnderlyingErrorKey] = error as NSError         let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)         // Replace this with code to handle the error appropriately.         // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.         NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")         abort()     }      return coordinator }() 

as well as an associated ManagedObjectContext:

lazy var ramManagedObjectContext: NSManagedObjectContext = {     // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.     let coordinator = self.ramStoreCoordinator     var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)     managedObjectContext.persistentStoreCoordinator = coordinator     managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy     return managedObjectContext }() 

I'm trying to execute a fetch request like so:

    let fetchRequest = NSFetchRequest(entityName: "Post")     let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)     do {         // Execute Batch Request         try ramManagedObjectContext.executeRequest(batchDelete)     } catch {         let updateError = error as NSError         print("\(updateError), \(updateError.userInfo)")     } 

the line:

try ramManagedObjectContext.executeRequest(batchDelete) 

crashes the app with the following output:

2016-04-30 23:47:40.271 Secret[2368:1047869] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unknown command type (entity: EntityName; predicate: ((null)); sortDescriptors: ((null)); type: NSManagedObjectIDResultType; ) >' * First throw call stack: (0x18145ae38 0x180abff80 0x1833710b0 0x18338991c 0x183391d64 0x101121a3c 0x10112d5f0 0x1833845bc 0x1832c1d5c 0x183354e04 0x10011abc4 0x10011947c 0x100092bf0 0x10009269c 0x1000926ec 0x186a1aac0 0x186a1b258 0x186901854 0x186904a4c 0x1866d4fd8 0x1865e0014 0x1865dfb10 0x1865df998 0x183f4da20 0x101121a3c 0x1011274e4 0x181410dd8 0x18140ec40 0x181338d10 0x182c20088 0x18660df70 0x1000fcba8 0x180ed68b8) libc++abi.dylib: terminating with uncaught exception of type NSException

3 Answers

Answers 1

NSBatchDeleteRequest should be executed on your ramStoreCoordinator, not ramManagedObjectContext, since it works directly with NSPersistenceStore class instance:

try persistentStoreCoordinator.executeFetchRequest(batchDelete, withContext: ramManagedObjectContext) 

Check this link for more details: https://developer.apple.com/videos/play/wwdc2015/220/

Hope it helped)

Answers 2

I had the exact same problem. Solved it by changing the in memory store to NSSQLiteStoreType . Have not done any research why this happens to in memory store, but I hope that solves your problem.

Answers 3

This error can occur when you rename some files outside XCode. To solve it you can just remove the files from your project (Right Click - Delete and "Remove Reference") You re-import the files in your project and everything will be ok !

If it didn't fix, try this

try persistentStoreCoordinator.executeFetchRequest(     batchDelete, withContext:context ) 

as NSBatchDeleteRequest is executed on the persistent store coordinator, not the managed object context.

Read More

Thursday, June 9, 2016

iOS - lightweight migration - unrecognized selector

Leave a Comment

I'm trying to do a lightweight migration. I made a new project following step by step this tutorial and I get things work. But now I'm in another app and everything seems go well but when I try to access to the new field then I get the following error.

[Promocion setEnlace:]: unrecognized selector sent to instance 0x7fafe40bceb0 

I have selected the correct model. enter image description here

This is the model Promocion.h

#import <Foundation/Foundation.h> #import <CoreData/CoreData.h>  /* @interface Promocion : NSManagedObject  @property (nonatomic, retain) NSNumber * id_promocion; @property (nonatomic, retain) NSString * descripcion; @property (nonatomic, retain) NSString * titulo; @property (nonatomic, retain) NSString * url; @property (nonatomic, retain) NSString * fecha_inicio; @property (nonatomic, retain) NSString * fecha_fin;  @end  */  NS_ASSUME_NONNULL_BEGIN  @interface Promocion : NSManagedObject  // Insert code here to declare functionality of your managed object subclass  //+(id)personaWithContext:(NSManagedObjectContext *)context;  @end  NS_ASSUME_NONNULL_END  #import "Promocion+CoreDataProperties.h" 

Promocion.m

#import "Promocion.h"   @implementation Promocion   - (NSComparisonResult)compare:(Promocion *)otherObject {      NSDateFormatter *dateformat = [[NSDateFormatter alloc] init];     [dateformat setDateFormat:@"MMMM dd,eeee HH:mm a z"];     NSDate *dateA = [dateformat dateFromString:self.fecha_inicio];     NSDate *dateB = [dateformat dateFromString:otherObject.fecha_inicio];      return [dateA compare:dateB]; }  @end 

And the following were auto-generated files Promocion+CoreDataProperties.h

#import "Promocion.h"  NS_ASSUME_NONNULL_BEGIN  @interface Promocion (CoreDataProperties)  @property (nullable, nonatomic, retain) NSString *descripcion; @property (nullable, nonatomic, retain) NSString *fecha_fin; @property (nullable, nonatomic, retain) NSString *fecha_inicio; @property (nullable, nonatomic, retain) NSNumber *id_promocion; @property (nullable, nonatomic, retain) NSString *titulo; @property (nullable, nonatomic, retain) NSString *url; @property (nullable, nonatomic, retain) NSString *enlace;  @end  NS_ASSUME_NONNULL_END 

Promocion+CoreDataProperties.m #import "Promocion+CoreDataProperties.h"

@implementation Promocion (CoreDataProperties)  @dynamic descripcion; @dynamic fecha_fin; @dynamic fecha_inicio; @dynamic id_promocion; @dynamic titulo; @dynamic url; @dynamic enlace;  @end 

AppDelegate.m

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {     if (_persistentStoreCoordinator != nil) {         return _persistentStoreCoordinator;     }      NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"data.sqlite"];      //Migration     NSDictionary *options = @{                               NSMigratePersistentStoresAutomaticallyOption  :@YES,                               NSInferMappingModelAutomaticallyOption        :@YES                               };      NSError *error = nil;     _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];     if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType                                                    configuration:nil                                                              URL:storeURL                                                         //options:nil                                                          options:options//automatic migration                                                            error:&error]) {         /*          Replace this implementation with code to handle the error appropriately.           abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.           Typical reasons for an error here include:          * The persistent store is not accessible;          * The schema for the persistent store is incompatible with current managed object model.          Check the error message to determine what the actual problem was.            If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.           If you encounter schema incompatibility errors during development, you can reduce their frequency by:          * Simply deleting the existing store:          [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]           * Performing automatic lightweight migration by passing the following dictionary as the options parameter:          @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}           Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.           */         NSLog(@"Unresolved error %@, %@", error, [error userInfo]);         //abort();     }      return _persistentStoreCoordinator; } 

Additional info

I have updated the version of the project.

Where I access the field called "enlace"

NSString* className = NSStringFromClass([Promocion class]); NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:context]; Promocion* obj = (Promocion*)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];  obj.id_promocion = [NSNumber numberWithInt:[[centro objectForKey:@"id_promo"] intValue]]; obj.titulo = [centro objectForKey:@"titulo"]; obj.descripcion = [centro objectForKey:@"descripcion"]; obj.url = [centro objectForKey:@"imagen"]; obj.fecha_inicio = [centro objectForKey:@"fecha_ini"]; obj.fecha_fin = [centro objectForKey:@"fecha_fin"]; obj.enlace = [centro objectForKey:@"enlace"]; 

What I've tried

I have changed Promocion.h to avoid use Promocion+CoreDataProperties.h but I still have the same error.

#import <Foundation/Foundation.h> #import <CoreData/CoreData.h>  @interface Promocion : NSManagedObject  @property (nonatomic, retain) NSNumber * id_promocion; @property (nonatomic, retain) NSString * descripcion; @property (nonatomic, retain) NSString * titulo; @property (nonatomic, retain) NSString * url; @property (nonatomic, retain) NSString * fecha_inicio; @property (nonatomic, retain) NSString * fecha_fin; @property (nonatomic, retain) NSString * enlace;  @end 

Another attempt

I recreate Promocion.h deleting the old one and creating a new one using Editor->Create NSOjecteManaged subclass. It looks like is working. But when I rollback using git to a last not updated core data commit and install it on a device and then upgrading the app with the last commit with new coredata model then start failing again. Damn I feel lot of frustration.

Partial Appdelegate.m

#pragma mark - Core Data stack  // Returns the managed object context for the application. // If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application. - (NSManagedObjectContext *)managedObjectContext {     if (_managedObjectContext != nil) {         return _managedObjectContext;     }      NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];     if (coordinator != nil) {         _managedObjectContext = [[NSManagedObjectContext alloc] init];         [_managedObjectContext setPersistentStoreCoordinator:coordinator];     }     return _managedObjectContext; }  // Returns the managed object model for the application. // If the model doesn't already exist, it is created from the application's model. - (NSManagedObjectModel *)managedObjectModel {     if (_managedObjectModel != nil) {         return _managedObjectModel;     }     NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel" withExtension:@"momd"];     _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];     return _managedObjectModel; }  // Returns the persistent store coordinator for the application. // If the coordinator doesn't already exist, it is created and the application's store added to it. - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {     if (_persistentStoreCoordinator != nil) {         return _persistentStoreCoordinator;     }      NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"data.sqlite"];      //Migration     NSDictionary *options = @{                               NSMigratePersistentStoresAutomaticallyOption  :@YES,                               NSInferMappingModelAutomaticallyOption        :@YES                               };      NSError *error = nil;     _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];     if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType                                                    configuration:nil                                                              URL:storeURL                                                         //options:nil                                                          options:options//automatic migration                                                            error:&error]) {         /*          Replace this implementation with code to handle the error appropriately.           abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.           Typical reasons for an error here include:          * The persistent store is not accessible;          * The schema for the persistent store is incompatible with current managed object model.          Check the error message to determine what the actual problem was.            If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.           If you encounter schema incompatibility errors during development, you can reduce their frequency by:          * Simply deleting the existing store:          [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]           * Performing automatic lightweight migration by passing the following dictionary as the options parameter:          @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}           Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.           */         NSLog(@"Unresolved error %@, %@", error, [error userInfo]);         //abort();     }      return _persistentStoreCoordinator; } 

3 Answers

Answers 1

I think that your bug is not related to lightweight-migration, since your application does't crash-OnLaunch (as for any structure changes without migration) but it provide you with a runtime error mainly related to your model-class.

I suspect that your code still use a old generated model-class or a old category on that old class without that missing attribute: ( #import "Promocion.h" or #import "Promocion+creation.h").

This is potentially true if your coreData & managedObjects-subclass had been created using a previous version of Xcode before Xcode 7. An easy check is to cmd+tap on any #import "Promocion.h" to see contents.

If is it the case here is a check list to fix the problem (else this may still help other facing similar issue) :

-Search and delete(or rename!) Promocion.h :old generated file that contains only dynamic attributes (generated using a version previous to Xcode7 version see details below).

-Check if a category had been created on that class.."Promocion+Xyz.h" and change all reference #import "Promocion+Xyz.h" to #import "Promocion.h"

-Recreate your managedObject subclass.

Context

Before Xcode7 whenever we create a managedObject subclass the .h contains dynamic attributes and we used to create a category on that class to add methods and meanwhile to be able to regenerate our managedObject subclass without losing our created methods since they are in a category.

From Xocde7 Apple reviewed that and now dynamic attibutes are in ClassName+CoreDataProperties.h to make it easy to work/add methods on the main class without the need to add a category, and meanwhile to be able to regenerate managedObject since only ClassName+CoreDataProperties.h is recreated.

So now whenver we recreate a managedObject subclass that previously was created by the old way we need to make sure that we reference #import "Promocion.h" the right file!.

Answers 2

It reads like the migration is not occurring and while the class files are probably correct, they are all relying on the underlying NSManagedObject to have the property.

Can you get a sqlite file from a device that has been updated to Model 2 and check the structure of the sqlite file and see if the new property is in place? If it isn't then this would point to the migration not firing and perhaps the wrong version of the model being defined as current.

Answers 3

Try changing the line in your AppDelegate's datamodel function:

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel" withExtension:@"momd"]; 

with

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel 2" withExtension:@"mom"]; 
Read More

Thursday, April 14, 2016

Need Help Diagnosing NSMergeConflict Error

Leave a Comment

I've been struggling to resolve an NSMergeConflict in an app I am working on. I know I could just change the merge policy, but I'm concerned about possibly corrupting the data since I don't know exactly which of the conflicting objects is correct and which is out of date. To make things even more complicated, the merge conflict is not actually in the object I am modifying and saving. Instead it appears to be coming from some related objects that I am not directly modifying.

The relationship graph looks something like this:

Order <<-----> Customer  Order <<-----> Rep  Rep   <<-----> Customer 

where Order is the object I am modifying and saving and Customer and Rep are the objects that the merge error is complaining about.

What I really can't figure out is how to debug this issue and track down where my data is getting out of sync. Does anyone know of a good way to get more data about what is going on in my code? Is there a way to log what is in all the persistence contexts and see where they are diverging?

Update: I changed the relationship between Order and Rep such that Rep no longer has a collection of Orders and that solved some of the issues. Unfortunately, that solution is not an option for the relationship between Order and Customer and I am still seeing errors there.

Here is a sample stacktrace of the error I am getting:

NSMergeConflict (0x1465972e0) for NSManagedObject (0x1462147a0) with objectID '0xd000000000a00008 <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/Customer/p40>' with oldVersion = 13 and newVersion = 14 and old object snapshot = { address = "{\n    cmpnts =     (\n                {\n            \"long_name\" = \"2318 W 5TH AVE\";\n            \"short_name\" = \"2318 W 5TH AVE\";\n            types =             (\n                \"street_address\"\n            );\n        },\n                {\n            \"long_name\" = GARY;\n            \"short_name\" = GARY;\n            types =             (\n                locality\n            );\n        },\n                {\n            \"long_name\" = IN;\n            \"short_name\" = IN;\n            types =             (\n                \"administrative_area_level_1\"\n            );\n        },\n                {\n            \"long_name\" = \"Lake County\";\n            \"short_name\" = \"Lake County\";\n            types =             (\n                \"administrative_area_level_2\"\n            );\n        },\n                {\n            \"long_name\" = \"46404-1331\";\n            \"short_name\" = \"46404-1331\";\n            types =             (\n                \"postal_code\"\n            );\n        }\n    );\n    lat = \"41.60221\";\n    lng = \"-87.366285\";\n    loc = \"2318 W 5TH AVE, GARY, IN, 46404-1331\";\n}"; buyingGroup = "<null>"; compositeName = "20/20 EYE SPECIALIST (#385468)"; created = "2014-10-29 19:36:43 +0000"; "created_by_id" = 5451215fe4b007ffb46431bf; "current_due" = 0; customer = 385468; customerTotal = "0xd0000000db74000a <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/CustomerTotals/p14045>"; detail = "0xd00000000504000c <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/CustomerDetail/p321>"; "device_address_section_key" = 2; "device_address_sort_key" = "2318 W 5TH AVE"; "device_city_section_key" = G; "device_city_sort_key" = GARY; "device_corp_ytd_gross" = 0; "device_created_section_key" = "01/01/1980"; "device_customer_section_key" = 3; "device_deleted" = 0; "device_exported" = "<null>"; "device_last_view" = "2016-04-12 17:04:51 +0000"; "device_latitude" = "41.60221"; "device_loaded" = "2016-04-12 15:25:07 +0000"; "device_longitude" = "-87.366285"; "device_name_section_key" = 2; "device_note_date" = "<null>"; "device_note_text" = "<null>"; "device_phone_section_key" = 2; "device_phone_sort_key" = 2198850116; "device_rolling_12_credits" = 0; "device_rolling_12_net" = 0; "device_rolling_12_rd" = 0; "device_rolling_12_rx" = 0; "device_state_section_key" = I; "device_state_sort_key" = IN; "device_updated" = "<null>"; "device_ytd_credits" = 0; "device_ytd_gross" = 0; "device_ytd_net" = 0; "device_ytd_rd" = 0; "device_ytd_rx" = 0; "device_zip_section_key" = 4; "device_zip_sort_key" = "46404-1331"; "dw_fax" = ""; "dw_open_date" = "1980-01-01 05:00:00 +0000"; "is_inherited" = 0; jobsonCensus = "0xd000000001a4000e <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/JobsonCensus/p105>"; "jobson_census_id" = 5494225284aec464730b3aa1; "last_order_date" = "<null>"; "last_sync" = "2016-04-12 15:58:20 +0000"; name = "20/20 EYE SPECIALIST"; primaryContact = "<null>"; "remote_id" = 545141cbe4b024c7a6033055; "updated_at" = "2015-09-15 11:48:11 +0000"; "updated_by_id" = "<null>"; } and new cached row = {     address = "{\n    cmpnts =     (\n                {\n            \"long_name\" = \"2318 W 5TH AVE\";\n            \"short_name\" = \"2318 W 5TH AVE\";\n            types =             (\n                \"street_address\"\n            );\n        },\n                {\n            \"long_name\" = GARY;\n            \"short_name\" = GARY;\n            types =             (\n                locality\n            );\n        },\n                {\n            \"long_name\" = IN;\n            \"short_name\" = IN;\n            types =             (\n                \"administrative_area_level_1\"\n            );\n        },\n                {\n            \"long_name\" = \"Lake County\";\n            \"short_name\" = \"Lake County\";\n            types =             (\n                \"administrative_area_level_2\"\n            );\n        },\n                {\n            \"long_name\" = \"46404-1331\";\n            \"short_name\" = \"46404-1331\";\n            types =             (\n                \"postal_code\"\n            );\n        }\n    );\n    lat = \"41.60221\";\n    lng = \"-87.366285\";\n    loc = \"2318 W 5TH AVE, GARY, IN, 46404-1331\";\n}";     buyingGroup = "<null>";     compositeName = "20/20 EYE SPECIALIST (#385468)";     created = "2014-10-29 19:36:43 +0000";     "created_by_id" = 5451215fe4b007ffb46431bf;     "current_due" = 0;     customer = 385468;     customerTotal = "0xd0000000db74000a <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/CustomerTotals/p14045>";     detail = "0xd00000000504000c <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/CustomerDetail/p321>";     "device_address_section_key" = 2;     "device_address_sort_key" = "2318 W 5TH AVE";     "device_city_section_key" = G;     "device_city_sort_key" = GARY;     "device_corp_ytd_gross" = 0;     "device_created_section_key" = "01/01/1980";     "device_customer_section_key" = 3;     "device_deleted" = 0;     "device_exported" = "<null>";     "device_last_view" = "2016-04-12 17:04:51 +0000";     "device_latitude" = "41.60221";     "device_loaded" = "2016-04-12 15:25:07 +0000";     "device_longitude" = "-87.366285";     "device_name_section_key" = 2;     "device_note_date" = "<null>";     "device_note_text" = "<null>";     "device_phone_section_key" = 2;     "device_phone_sort_key" = 2198850116;     "device_rolling_12_credits" = 0;     "device_rolling_12_net" = 0;     "device_rolling_12_rd" = 0;     "device_rolling_12_rx" = 0;     "device_state_section_key" = I;     "device_state_sort_key" = IN;     "device_updated" = "<null>";     "device_ytd_credits" = 0;     "device_ytd_gross" = 0;     "device_ytd_net" = 0;     "device_ytd_rd" = 0;     "device_ytd_rx" = 0;     "device_zip_section_key" = 4;     "device_zip_sort_key" = "46404-1331";     "dw_fax" = "";     "dw_open_date" = "1980-01-01 05:00:00 +0000";     "is_inherited" = 0;     jobsonCensus = "0xd000000001a4000e <x-coredata://1F648314-CA44-4DA5-B437-07BFCB96D1E9/JobsonCensus/p105>";     "jobson_census_id" = 5494225284aec464730b3aa1;     "last_order_date" = "<null>";     "last_sync" = "2016-04-12 16:06:52 +0000";     name = "20/20 EYE SPECIALIST";     primaryContact = "<null>";     "remote_id" = 545141cbe4b024c7a6033055;     "updated_at" = "2015-09-15 11:48:11 +0000";     "updated_by_id" = "<null>"; } 

1 Answers

Answers 1

It seems to my that the relationship from Customer to Rep is redundant, unless it is completely unrelated to the Order entity. If Reps are linked to Customers only via the Orders, you could get the Reps for a customer like this:

Set(arrayLiteral: customer.orders.map { $0.rep }) 

As for logging more debug output you could switch on sql debugging, although you would have to extrapolate what Core Data is doing. (Add -com.apple.CoreData.SQLDebug 1 to the Run Scheme.)

If updating orders affects your Users or Reps you could check if you have some delete rule with unintended consequences.

Read More

Friday, April 1, 2016

How to encrypt data in core data (sqllite) in OS-X application

Leave a Comment

I have found that if I use transformable type of attributes and NSXMLStoreType my data is encrypted, that is attributes that has been of transformable type, are not readable. There is no need of doing anything else, no code is required. Please note that I am working on OS-X application that uses core data.

However, if I change my store type to NSSQLiteStoreType, that is not the case.

I can open the database with sqllitebrowser, select the transformable field, and If I click on export button, in the generated text file, I can read the value normally, that is the value (data) is not encrypted.

I have asked the same question about 4 months ago and I get no answer.

Also, I have found this post here on stackoverflow.

You can encrypt individual properties in your Core Data model entities by making them transformable properties, then creating an NSValueTransformer subclass which will encrypt and decrypt the data for that property.

Unlucky for me, the author of the answer, @Brad Larson, didn't provide an simple example of how this can be done.

Can anyone provide any sample code of how I can encrypt transformable properties so that It' cant be readable in any way?

1 Answers

Answers 1

you could do something like shown here Cross-platform-AES-encryption

add a new Objective-C file to project select category and NSData class call it Additions

NSData+Additions.h

#import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h>  @interface NSData (Additions)  #pragma mark - data encryption  + (NSData *)encrypt:(NSData *)plainText key:(NSData *)key iv:(NSData *)iv; + (NSData *)decrypt:(NSData *)encryptedText key:(NSData *)key iv:(NSData *)iv;  + (NSData *)dataFromHexString:(NSString *)string; + (NSData *)sha256forData:(id)input;  + (NSData *)generateRandomIV:(size_t)length;  @end 

NSData+Additions.m

#import "NSData+Additions.h"  @implementation NSData (Additions)  + (NSData *)encrypt:(NSData *)dataToEncrypt key:(NSData *)key iv:(NSData *)iv {      NSUInteger dataLength = [dataToEncrypt length];      size_t buffSize = dataLength + kCCBlockSizeAES128;     void *buff = malloc(buffSize);      size_t numBytesEncrypted = 0;      CCCryptorStatus status = CCCrypt(kCCEncrypt,                                      kCCAlgorithmAES128,                                      kCCOptionPKCS7Padding,                                      [key bytes], kCCKeySizeAES256,                                      [iv bytes],                                      [dataToEncrypt bytes], [dataToEncrypt length],                                      buff, buffSize,                                      &numBytesEncrypted);      if (status == kCCSuccess) {         return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];     }      free(buff);     return nil; }  + (NSData *)decrypt:(NSData *)encryptedData key:(NSData *)key iv:(NSData *)iv {      NSUInteger dataLength = [encryptedData length];      size_t buffSize = dataLength + kCCBlockSizeAES128;      void *buff = malloc(buffSize);      size_t numBytesEncrypted = 0;     CCCryptorStatus status = CCCrypt(kCCDecrypt,                                      kCCAlgorithmAES128,                                      kCCOptionPKCS7Padding,                                      [key bytes], kCCKeySizeAES256,                                      [iv bytes],                                      [encryptedData bytes], [encryptedData length],                                      buff, buffSize,                                      &numBytesEncrypted);     if (status == kCCSuccess) {         return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];     }      free(buff);     return nil; }  + (NSData *)dataFromHexString:(NSString *)string {      NSMutableData *stringData = [[NSMutableData alloc] init];     unsigned char whole_byte;     char byte_chars[3] = {'\0','\0','\0'};      for (int counter = 0; counter < [string length] / 2; counter++) {         byte_chars[0] = [string characterAtIndex:counter * 2];         byte_chars[1] = [string characterAtIndex:counter * 2 + 1];         whole_byte = strtol(byte_chars, NULL, 16);         [stringData appendBytes:&whole_byte length:1];     }      return stringData; }  + (NSData *)sha256forData:(id)input {     NSData *dataIn;      if ([input isKindOfClass:[NSString class]]) {         dataIn = [input dataUsingEncoding:NSUTF8StringEncoding];     } else if ([input isKindOfClass:[NSData class]]) {          NSUInteger dataLength = [input length];         NSMutableString *string = [NSMutableString stringWithCapacity:dataLength * 2];         const unsigned char *dataBytes = [input bytes];          for (NSInteger idx = 0; idx < dataLength; ++idx)             [string appendFormat:@"%02x", dataBytes[idx]];          dataIn = [string dataUsingEncoding:NSUTF8StringEncoding];     }      NSMutableData *macOut = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];      CC_SHA256(dataIn.bytes, (CC_LONG)[dataIn length],  [macOut mutableBytes]);      return macOut; }  + (NSData *)generateRandomIV:(size_t)length {     NSMutableData *data = [NSMutableData dataWithLength:length];      SecRandomCopyBytes(kSecRandomDefault, length, [data mutableBytes]);      return data; }  @end 

then you are going to need username, password, iVector and some random salt. in the salt i have used replace # with random characters but make sure that if u use % do not forget to double it %% otherwise it will be incomplete format specifier warning.

in your class use it like this

#define CC_USERNAME         @"secretName" #define CC_PASSWORD         @"secretPassword" #define CC_SALTED_STRING    [NSString stringWithFormat:@"####################%@#####################", CC_PASSWORD] 

then create NSData representation of your salted string ran through SHA256

NSData *hash = [NSData sha256forData:CC_SALTED_STRING]; 

next step is to generate 16 bytes of random generated iVector data

NSData *iVector = [NSData generateRandomIV:16]; 

and use these objects to encrypt your string. create a NSMutableData object with first 16 bytes of iVector data (make sure u use iVector object and do not generate new random or you will be unable to decrypt).

NSString *message = @"my secret message to the world"; NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding];  NSMutableData *encryptedData = [[NSMutableData alloc] initWithData:iVector];  NSData *payLoad = [NSData encrypt:messageData key:hash iv:iVector];  [encryptedData appendData:payLoad]; 

to decrypt, separate first 16 bytes and the rest of data and use it with the hash.

NSData *pureData = [encryptedData subdataWithRange:NSMakeRange(16, [encryptedData length] - 16)]; NSData *extractedVector = [encryptedData subdataWithRange:NSMakeRange(0, 16)];  NSData *decryptedData = [NSData decrypt:pureData key:hash iv:extractedVector];  NSString *decryptedMessage = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; 

you can do some extra md5 on the hash or even pack encrypted data with zlib before storing it.

enjoy your custom made crypto.

Read More

Monday, March 28, 2016

Unable to share data using Shared App Groups for Today Extension

Leave a Comment

I am trying to create a today extension that displays data from the parent app by using a shared app group container, and then adding the persistent store to a context.

  • Add Today Extension Target
  • Turn on app groups for parent app and extension and select same group
  • Add Today Extension as Target Membership for Data model and entities
  • Add Persistent store to context
  • Fetch Objects

I get no errors but the extension does not seem to be fetching any results. Does anybody have any suggestions where I may be going wrong ?

Heres what I am doing in the extension TodayViewController

class TodayViewController: UIViewController, NCWidgetProviding {  var context: NSManagedObjectContext!  @IBOutlet weak var table: UITableView! var objectsArray = [Objects]()  override func viewDidLoad() {     super.viewDidLoad()      let fileManager = NSFileManager.defaultManager()     var containerPath = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.com.Company.AppName")      containerPath = containerPath?.URLByAppendingPathComponent("SingleViewCoreData.sqlite")     let modelURL = NSBundle.mainBundle().URLForResource("AppName", withExtension: "momd")     let model = NSManagedObjectModel(contentsOfURL: modelURL!)     let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model!)     do {         try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: containerPath, options: nil)     } catch {      print("yellow")     }      context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)     context.persistentStoreCoordinator = coordinator       let moc = context     let request = NSFetchRequest(entityName: "Objects")     request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]       do {         try             self.objectsArray = moc.executeFetchRequest(request) as! [Objects]             print ("objects count \(objectsArray.count)")     } catch {         // failure         print("Fetch failed")     }      self.table.reloadData() }  // MARK: - Table view data source  func numberOfSectionsInTableView(tableView: UITableView) -> Int {     // #warning Potentially incomplete method implementation.     // Return the number of sections.     //return sectionsArray.count      return 1 }   func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {      return self.objectsArray.count }  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {    let cell = table.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!     cell.textLabel!.textColor = UIColor.whiteColor()    cell.textLabel!.text = self.objectsArray[indexPath.row].title     return cell  }  func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {     // Perform any setup necessary in order to update the view.      // If an error is encountered, use NCUpdateResult.Failed     // If there's no update required, use NCUpdateResult.NoData     // If there's an update, use NCUpdateResult.NewData      completionHandler(NCUpdateResult.NewData) }  } 

1 Answers

Answers 1

In Apple's App Extension Programming Guide --Sharing Data with Your Containing App

Even though an app extension bundle is nested within its containing app’s bundle, the running app extension and containing app have no direct access to each other’s containers.

So they wouldn't be able to share data directly,even if you

Add Today Extension as Target Membership for Data model and entities

Instead,they communicated with each other by shared container(App groups) indirectly,which you had already setup in step 2.

enter image description here

So the solution is :

  1. In your Contraning App,create the sql data model in your shared container ,insert data in this database.Adjust your code in todayExtension ,and fetch data.
  2. Or Using NSUserDefaults to share data instead.

like this:

// Create and share access to an NSUserDefaults object NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: @"com.example.domain.MyShareExtension"];  // Use the shared user defaults object to update the user's account [mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"]; 
Read More

Thursday, March 10, 2016

How to ignore changes in mergeChangesFromContextDidSaveNotification in NSManagedObjectContextWillSaveNotification

Leave a Comment

I am using a Private Managed Object Context to create some new objects into the persistent store, then after saving the private MOC, merging them into the main MOC using mergeChangesFromContextDidSaveNotification. This works fine, and updates the UI as required, and the NSManagedObjectContextWillSaveNotification is NOT invoked here for the mainMOC.

Then I make some changes to the mainMOC using the UI, and listen to NSManagedObjectContextWillSaveNotification. The notification is posted, but it contains not only the edits I made, but also the objects that were merged in from the PrivateMOC using mergeChangesFromContextDidSaveNotification.

Is there a way to ignore the changes that were merged in from another context into the mainContext, on subsequent contextDidChange notifications?

Here is the setup:

- (void) loadData {    privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];     privateContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator;     [[NSNotificationCenter defaultCenter] addObserver:self                                          selector:@selector(contextWillSave:)                                              name:NSManagedObjectContextWillSaveNotification                                            object: self.mainContext];     NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:record.recordType inManagedObjectContext: self.privateContext];     // fill in object     if ([self.privateContext hasChanges]) {        [self savePrivateContextAndMergeWithMainContext: self.privateContext];    } }  - (void) savePrivateContextAndMergeWithMainContext: (NSManagedObjectContext *) privateContext {     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(privateContextDidChange:) name:NSManagedObjectContextDidSaveNotification object:privateContext];     __block NSError *error = nil;     [privateContext performBlockAndWait:^{         NSLog(@"PrivateContext saved");         [privateContext save:&error];     }];       [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:privateContext];      if (error) {         NSLog(@"error = %@", error);     } }  - (void) privateContextDidChange: (NSNotification *) notification{      [self.mainContext performBlockAndWait:^{         NSLog(@"merged into mainContext");         [self.mainContext mergeChangesFromContextDidSaveNotification:notification];     }]; } 

This works fine and saving the private context and merging into the mainContext doesn't trigger a contextWillSave notification. But on editing the data from the UI (on the main MOC) triggers the notification and includes the data that was previously saved using the private MOC.

Hope that's clear. Let me know if I should include anything else.

-- UPDATE --

Seems like the problem is with specifically deleting objects from the private context. After deleting from the private context, and calling mergeChangesFromContextDidSaveNotification on the main MOC, the mainMoc's deletedObjects set still shows the object that was deleted. This doesn't happen with inserts or updates in the private context. Is this documented anywhere? What could be the workaround?

2 Answers

Answers 1

Core Data has been around for a number of years now, and over that time the concurrency model has evolved.

The "thread confinement" and "locking" concurrency models used notifications to communicate changes between contexts. When a context was saved a notification would be broadcast with information about the changes. That information could be used to merge the changes from one context into others. This never scaled particularly well and was often a major pain point for applications. The "locking" and "thread confinement" models have been obsolete for a number of years now.

When "queue confinement" was introduced the concept of "nested contexts" was introduced along with it. A context could have a parent context, and when the child context is saved those changes are automatically communicated to the parent (and no further). This is the recommended way to communicate changes between contexts when using queue confinement, which you are.

It is not recommended that you use mergeChangesFromContextDidSaveNotification: with a NSPrivateQueueConcurrencyType context.

Core Data performs a lot of internal bookkeeping and change tracking on your behalf. Normally during a save operation that information is aggregated and coalesced - this is part of the processPendingChanges operation. When the save operation is performed inside a performBlockAndWait: this may not happen, or may not be complete - which results in the changes (and any change notification) being incomplete or not happening at all. performBlockAndWait: is reentrant, which is not compatible with the processPendingChanges process. Using performBlock: instead will result in more consistent behavior.

If you use nested contexts to communicate changes rather than notifications your issue as you describe in your question will be solved.

Answers 2

Modifying privateContextDidChange like this ...

- (void) privateContextDidChange: (NSNotification *) notification{     if (notification.object == PrivateMOC) {         [self.mainContext performBlockAndWait:^{             NSLog(@"merged into mainContext");             [self.mainContext mergeChangesFromContextDidSaveNotification:notification];         }];     } } 

... where PrivateMOC is the reference to the Private Managed Object Context?

Read More