Fetching data from Core Data on application launch - swift

I need to check for items in my Core Data when the app launches. this is what I wrote so far, but I don't think this is the best practice (although it is seems to be working) is there any other way? better way to acheive what I need?
func getMealsFromCoreData() -> [Meal]{
var retrivedMeals = [Meal]()
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let managedContext = appDelegate?.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Meal")
do {
let result = try managedContext?.fetch(fetchRequest)
for data in result as! [NSManagedObject]{
retrivedMeals.append(data as! Meal)
}
}catch{
print("Failed to fetch.")
}
return retrivedMeals
}
func uploadMeals(){
var mealsToUpload = [Meal]()
let dispatchQueue = DispatchQueue(label: "mealQueue", qos: .background)
dispatchQueue.async {
mealsToUpload = self.getMealsFromCoreData()
for meal in mealsToUpload {
print(meal)
}
}
}
Upload meal should upload to meals to the screen after fetching everything (maybe not the best function name).
This is what I wrote in the AppDelegate in didFinishLaunchingWithOptions:
DataManager.manager.uploadReports()
I get in getMealsFromCoreData a purple warnning that says appDelegate should run on the main queue.
I'm really confused with this CoreData + Moving CoreData fetch from the main thread to the background.
Would really appericiate your help guys.
Am I doing something wrong? What is the best practice for this?

Well, the warning says what you are doing wrong.
The best practice depends on the use case. But it is certainly not fetching all data on application start. Fetch the data when you need it, as much data as you need (to display).
Maybe have a NSFetchedResultsController to maintain the data for your view if that is an option for you.

Related

fetch all the saved binary data core data object names [duplicate]

My swift code below saves 3 images. What I want to do is overwrite iamgedata2 is func press. Imagedata2 should be replaced with Gwen. So the order should be Gwen Gwen Gwen instead of Gwen gwen2 Gwen. I don't know what really to put in func press to achieve this goal.
import UIKit; import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .orange
let gwen = UIImage(named: "blank")
let gwen2 = UIImage(named: "g.jpg")
if let imageData = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData)
}
if let imageData2 = gwen2.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData2)
}
if let imageData3 = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData3)
}
}
#objc func press(){
CoredataHandler.shareInstance.saveImage(data: 1)
return
}
}
class CoredataHandler : NSManagedObject {
static let shareInstance = CoredataHandler()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func saveImage(data: Data) {
let imageInstance = Information(context: context)
imageInstance.pic = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
}
If you want a Core Data entity that can store more than one image, you have a few options:
Declare multiple image properties
Instead of just having a pic property, have more than one. As many as you like. Name them pic1, pic2, pic3, etc, or whatever seems best for your app. In code, read or write whichever makes sense at the time.
This is easy but not flexible, since your code can only save up to the number of attributes you declare in the model.
Use an array property with transformable
With a transformable attribute you can store any data that can be converted to a Data. In your case you'd do something like this:
Two things to notice: The type is transformable, and the custom class is [Data]. Xcode will generate code where the property type is [Data]?. You can save as many Data blobs as you want in it, representing however many images you want.
This is also easy but may use a lot of memory, because you can't access one image without loading all of them into memory at once. If you always load all of them anyway then it's no different. If you often load only one of them, this technique might use a lot more memory (e.g. 4 images would mean around 4x as much memory used).
Use a separate entity to hold the image and a to-many relationship
With this approach you'd create a new Core Data entity that only holds the image. Your existing entity would have a to-many relationship to this entity. You'd create as many image-only instances as you want, and the relationship would mean they were all available at any time.
You would probably want to make sure the to-many relationship is ordered, otherwise the images would be an unordered set that could be in any order.
This is a little more complex to write but it's flexible and doesn't have the potential memory problems of other approaches.

Core data async fetch ends up on the main thread

I am trying to execute an asynchronous request as part of a search result updater in my app.
I wrote the following code
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else {return}
let threadingContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
threadingContext.parent = self.context
DispatchQueue.global(qos: .userInitiated).async {
let fetchRequest = MyObject.fetchRequest() as NSFetchRequest<MyObject>
fetchRequest.predicate = get_predicate(text)
do {
let objects = try threadingContext.fetch(fetchRequest).map({ object in
return object.objectID
})
}
catch {return}
DispatchQueue.main.async {
// Pass results to the search view controller
}
}
}
but the UI is still slow (even if I don't do any display update), and looking at the Time profiler, I see that my main thread is spending 80% of its time on the following:
So it seems that my request is still being dispatched onto the main thread, which I don't understand. Would anyone see my mistake?
(I tried a few various on the above e.g. using threadingContext.perform but for the same result)
Ok, I understood it, and I should have read Apple's documentation, but basically
If a context’s parent store is another managed object context, fetch and save operations are mediated by the parent context instead of a coordinator.
This is slightly subtle, but my construction would have been useful if the operations performed on the fetch request, rather than the fetch request itself, had been slow.
The solution is to set threadingContext.persistentStoreCoordinator instead.

performBackgroundTask Fault Error when fetching data in Core Data

I am encoutering a strange issue:
I am using a backgroundFetch to fetch the data from Core Data.
func fetchDataFromCoreData(completion:#escaping()->Void)
{
let appdel = UIApplication.shared.delegate as! AppDelegate
let context = appdel.persistentContainer.viewContext
appdel.persistentContainer.performBackgroundTask { (context) in
let fetchReq = NSFetchRequest<NSFetchRequestResult>(entityName: "FreeApps")
do
{
let data = try context.fetch(fetchReq) as? [NSManagedObject]
self.resultData = data
print(self.resultData ?? "data is empty")
DispatchQueue.main.async{
completion()
}
}
catch
{
print("fetch error")
}
}
}
Now in my view Controller, in my table cell:
let myDict = itunesViewModelObj.resultData?[indexPath.row] as? NSManagedObject
print(myDict?.value(forKey: "name") as? String ?? "no name")myDict shows as fault but valefor key comes nil
Now if I comment the performBackgroundTask line data comes properly.
Please help as what can be the issue with backgroundTask.
Any suggestions will be highly appreciated!
Thanks in advance!
The PersistentContainer operates on the main queue. As the name of the property implies, this managed object context is designed to be used in combination with the application's user interface. Maybe you need to dispatch back to the main queue to interact with UIApplicationDelegate and PersistentContainer.
PerformBackgroundTask is generally used for updates to core data. If you are doing fetches you should use the main viewContext.
Maybe using...
DispatchQueue.main.async {
// your code
}

Show indicator when save core data Swift

I have a button to save picture data in core data but when I push it, it is freezing because size of the data is big. I did try to use dispatch_async but it didn’t work. How do I create the icon/indicator showing that it is loading/bookmarking rather than just freezing?
#IBAction func save() {
let content = self.foodMenu?["content"].string
let urlString = self.foodMenu?["thumbnail_images"]["full"]["url"]
let urlshare = NSURL(string: urlString!.stringValue)
let imageData = NSData(contentsOfURL: urlshare!)
let images = UIImage(data: imageData!)
dispatch_async(dispatch_get_main_queue(), {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
self.foodClass = NSEntityDescription.insertNewObjectForEntityForName("Foods",
inManagedObjectContext: managedObjectContext) as! Foods
self.foodClass.content = content
self.foodClass.image = UIImageJPEGRepresentation(images, 1)
var e: NSError?
if managedObjectContext.save(&e) != true {
println("insert error: \(e!.localizedDescription)")
return
}
}
First, it is unlikely it is the save that is slow. I would suspect that your creation of the JPEG representation is the slow part.
Second, you are wanting to hide a problem by putting up a spinner. That really is bad for the user experience. Far better to do the following (yes it is more code);
Move your image creation and saving to a background queue.
Restructure your Core Data stack so that your saves to disk are on a private queue.
This involves using a background queue and multiple contexts in Core Data but getting this data processing off the User Interface thread is the right answer.

Core Data stack implementation for iCloud sync in a UITabBarController app (Swift 1.2)

I've spent the last 4 days trying to implement a proper Core Data stack with iCloud sync for my Swift 1.2 app, but I can really use some help.
Before, I was using a global Managed Context accessed from everywhere in the app; knowing that it was a bad implementation, now that I'm adding iCloud sync I decided to get rid of it, even though the app was working fine.
So far, I've implemented a new, working Core Data stack with decent - but not perfect - cloud sync between devices.
Now I face two issues:
Sometimes, a few objects don't sync.
Given the particular structure of my app, which I'll explain in a moment, I have no idea how and where in my code I should handle the notifications that Core Data sends when the user logs in or out of iCloud.
But, before tackling those problems, I'd really appreciate - if appropriate - some validation of the work I've done so far and it is mainly for some confirmations that I'm writing this: since I've already spent a lot of time changing my Core Data stack, before going forward I'd like to know if I'm propagating the context properly (the structure of my app doesn't conform to any tutorial I found online, so I had to improvise a bit), or if I made some basic mistakes that will compromise reliable syncing or the future development.
My app is structured as follow:
UITabBarViewController as initial ViewController
1st tab: UIViewController (shown when the app starts)
2nd tab: a UITableViewController embedded in a UINavigationController
3rd tab: another UITableViewController embedded in another UINavigationController
I have a CoreDataStack.swift class with the following code:
import CoreData
#objc class CoreDataStack : Printable {
let context : NSManagedObjectContext
let psc : NSPersistentStoreCoordinator
let model : NSManagedObjectModel
let store : NSPersistentStore?
var description : String {
return "context: \(context)\n" + "model: \(model)"
}
var applicationDocumentsDirectory : NSURL = {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
return urls[0]
}()
init() {
let modelURL = NSBundle.mainBundle().URLForResource("MyDataModel", withExtension:"momd")
model = NSManagedObjectModel(contentsOfURL: modelURL!)!
psc = NSPersistentStoreCoordinator(managedObjectModel: model)
context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
context.persistentStoreCoordinator = psc
let documentsURL = applicationDocumentsDirectory
let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")
let options = [NSPersistentStoreUbiquitousContentNameKey: "MyApp", NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)
if store == nil {
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict as [NSObject : AnyObject])
println("Error adding persistent store: \(error), \(error!.userInfo)")
abort()
}
}
func saveContext() {
var error: NSError? = nil
if context.hasChanges && !context.save(&error) {
println("Could not save: \(error), \(error!.userInfo)")
}
}
var updateContextWithUbiquitousContentUpdates: Bool = false {
willSet {
ubiquitousChangesObserver = newValue ? NSNotificationCenter.defaultCenter() : nil
}
}
private var ubiquitousChangesObserver : NSNotificationCenter? {
didSet {
oldValue?.removeObserver(self, name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
ubiquitousChangesObserver?.addObserver(self, selector: "persistentStoreDidImportUbiquitousContentChanges:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
}
}
func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) {
println("Merging ubiquitous content changes")
context.performBlock {
self.context.mergeChangesFromContextDidSaveNotification(notification)
}
}
}
In my AppDelegate.swift I added the following code just under var window: UIWindow?:
lazy var coreDataStack = CoreDataStack()
coreDataStack.updateContextWithUbiquitousContentUpdates = true
// The following code is the way I found to propagate the managed context of the stack instantiated above in all the ViewControllers of the UITabBarController, including those embedded in the two NavigationControllers;
// since in the future I'll probably need some flexibility in term of adding / rearranging the VCs in the TabBar, I kind of like this way to pass around the context.
// I could have also passed the context to the CustomTabBarViewController and from there do the same thing, but I figured I could just pass the context from AppDelegate, since I already can access all the ViewControllers from here with the following code.
var tabBarController = self.window!.rootViewController as! CustomTabBarViewController
for eachViewController in tabBarController.viewControllers! {
if eachViewController.isKindOfClass(CustomViewController){
(eachViewController as! CustomViewController).passedManagedContext = coreDataStack.context // Context is passed to the VC of 1st tab
}
if eachViewController.isKindOfClass(UINavigationController){
var firstNavController = tabBarController.viewControllers![1] as! UINavigationController
for tvc in firstNavController.viewControllers! {
if tvc.isKindOfClass(FirstCustomTableViewController) {
(tvc as! FirstCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 2
}
}
var secondNavController = tabBarController.viewControllers![2] as! UINavigationController
for tvc in secondNavController.viewControllers! {
if tvc.isKindOfClass(SecondCustomTableViewController) {
(tvc as! SecondCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 3
}
}
}
}
// Of course, in applicationDidEnterBackground: and applicationWillTerminate: I save the context; obviously, I also save the context, when appropriate, from the other ViewControllers.
With this structure in place, I instantiate my stack in AppDelegate and from there I propagate it to the 3 elements of the TabBar; from those, I again propagate the context to every other ViewController I present. I logged to the console the context everywhere and I can confirm that it is always the same.
As a matter of fact, the app with this code works.
I can't say it is perfect because, as I said, sometimes a few objects don't sync, but I suspect the cause of those objects not syncing is another (briefly, I have 2 NSManagedObject subclasses; the objects of subclass1 have an object of subclass2 as property; if I create a new subclass1 object using an existing subclass2 object as property, sync is fine; if I also create a new subclass2 object, save it and immediately set it as property of subclass1, sometimes the subclass2 object doesn't sync on the other device, while the subclass1 does and then misses that property... I can work on that later).
Before digging into this sync issue, I'd really love to know if the work I've done so far with the stack makes sense, or if it is horrible and needs to be canned.
Then, if all the code above is not horrible and if the reason of the occasional missed sync of objects would turn out to be the one I suspect, comes the other issue, and it is a big one: where do I put the code to handle the notifications that occurr when the user logs in or out from iCloud (NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification)?
I tried to put methods I've written (without actual functionality, at the moment I only log something to the console to know that I got there) based on Core Data by Tutorials book in both my AppDelegate and my CoreDataStack class, but in both cases when I log in or out from iCloud while the app is running, the app crashes without a single line in the console, so I have no idea of the issue.
Maybe I should put the methods to handle these notifications in all the ViewControllers, since the fetch requests happen there and UI is updated from those classes, but I'm not passing the entire coreDataStack objects around, only the context... so I'm missing something. Should I pass the entire stack, not only the context? Is it okay to handle those notifications from my CoreDataStack, or should I do it from AppDelegate?
Any help would really be appreciated...
Thanks in advance and, please, excuse if my question is not clear (I'm quite a beginner and english is not my main language...).
Also, thank you for your time reading this long question!
#cdf1982
I think the problem is that iCloud + CD never worked properly. It's not a developer code issue, the problem is the Apple implementation of iCloud + CD that simply fails.