Modify Realm Object from background thread cannot update on main thread immeditly - multithreading

Modify Realm Object from background thread cannot update on main thread immeditly
My code is like this:
class demo: NSObject {
var mail: OutMailModel?
func demotest() {
let realm = try! Realm()
let mailID = 10001
self.mail = realm.object(ofType: OutMailModel.self,forPrimaryKey: mailID)
DispatchQueue.global().async {
let realm = try! Realm()
if let myMail = realm.object(ofType: OutMailModel.self,forPrimaryKey: mailID) {
try! realm.write {
myMail.message = "xxxx"
}
}
DispatchQueue.main.async {
NSLog("mail.message:\(self.mail?.message)") // message not change
}
}
}
}
event this not work:
DispatchQueue.main.async {
let realm = try! Realm()
if let myMail = realm.object(ofType: OutMailModel.self,forPrimaryKey: mailID) {
NSLog("mail.message:\(myMail.message)") // message not changed
}
}
what's wrong is the code? Any one help?

When updating the realm from a background thread, the changes might not be visible because the realm isn't being updated with the new information. From the docs:
On the main UI thread (or any thread with a runloop) objects will
automatically update with changes from other threads between each
iteration of the runloop. At any other time you will be working on the
snapshot, so individual methods always see a consistent view and never
have to worry about what happens on other threads.
You can call realm.refresh() to force a refresh of the realm:
func demotest() {
...
DispatchQueue.global().async {
let realm = try! Realm()
if let myMail = realm.object(ofType: OutMailModel.self,forPrimaryKey: mailID) {
try! realm.write {
myMail.message = "xxxx"
}
}
realm.refresh();
DispatchQueue.main.async {
NSLog("mail.message:\(self.mail?.message)") // message not change
}
}
}

Related

Multiple duplicate NSPersistentStoreRemoteChange notifications fired from CloudKit + CoreData

I am seeing .NSPersistentStoreRemoteChange notifications being received multiple times, sometimes up to 10 times.
I don't think this is harmful, but best case it's using up processing power.
So my questions are:
Is this harmful?
Can I prevent it?
If not, is there a recommended way to ignore duplicate notifications?
--
I have the following code to setup my container. This is contained in the initialiser of a singleton and I have confirmed that it is called once.
guard let modelURL = Bundle(for: type(of: self)).url(forResource: momdName, withExtension:"momd"),
let mom = NSManagedObjectModel(contentsOf: modelURL)
else {
fatalError("🔐 Error loading model from bundle")
}
let containerURL = folderToStoreDatabaseIn.appendingPathComponent("Model.sqlite")
container = NSPersistentCloudKitContainer(name: momdName, managedObjectModel: mom)
guard let description = container.persistentStoreDescriptions.first else {
fatalError("🔐 ###\(#function): Failed to retrieve a persistent store description.")
}
description.url = containerURL
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
super.init()
// this must be called after super.init()
// ***** ADD OBSERVER *****
NotificationCenter.default.addObserver(self,
selector: #selector(updatedFromCKCD(_:)),
name: .NSPersistentStoreRemoteChange,
object: container.persistentStoreCoordinator)
if let tokenData = try? Data(contentsOf: tokenFile) {
do {
lastToken = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: tokenData)
} catch {
Logger.error("🔐 ###\(#function): Failed to unarchive NSPersistentHistoryToken. Error = \(error)")
}
}
The code to process these changes:
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
#objc func updatedFromCKCD(_ notifiction: Notification) {
let fetchHistoryRequest = NSPersistentHistoryChangeRequest.fetchHistory(
after: lastToken
)
let context = container.newBackgroundContext()
guard
let historyResult = try? context.execute(fetchHistoryRequest)
as? NSPersistentHistoryResult,
let history = historyResult.result as? [NSPersistentHistoryTransaction]
else {
Logger.error("⛈ Could not convert history result to transactions")
assertionFailure()
return
}
Logger.debug("⛈ Found cloud changes since: \(self.lastToken?.description ?? "nil")")
DispatchQueue.main.async {
// look for particular set of changes which require the user to take action
...
}
}

NSPersistentContainer, performBackgroundTask, calling perform does nothing

I'm just started working on a new project and thought I'd try out Core Data's NSPersistentContainer instead of writing my own stack.
I just stumbled upon this issue where calling the perform method of the managedObjectContext actually does nothing if the task was started as part of NSPersistentContainer's performBackgroundTask.
Here's a snippet of what I'm currently doing to demonstrate the issue. Note that I have a DispatchGroup to ensure that the tasks are performed in sequence.
// DataImporter.swift
class func importData(url: URL, context: NSManagedObjectContext, completion: () -> ()) {
context.perform {
// Code inside here never gets call
DispatchQueue.main.async(execute: {
completion()
})
}
}
// ViewController.swift
func multipleImportTasks() {
persistentContainer.performBackgroundTask { managedObjectContext in
let group = DispatchGroup()
group.enter()
let fileUrl1 = Data(someData)
DataImporter.importData(fileUrl1, context: managedObjectContext, completion: {
group.leave()
})
group.wait()
group.enter()
let fileUrl2 = Data(someData)
DataImporter.importData(fileUrl2, context: managedObjectContext, completion: {
group.leave()
})
group.notify(queue: DispatchQueue.main, execute: {
print("all done")
})
}
}
Its because of group.wait() call. group.wait() will block current thread and context.perform will also try run on same thread.

iOS10 & Switt3 - Core Data not saving nor return results

I am building an app with Core Data. it has always worked for me so far. Recently,
I get not result. no error. it seems that no data is persisted. has anyone ever encountered this weird malfunction?
My viewcontroller: to display contacts list
import UIKit
import CoreData
class ContactsTableViewController: UITableViewController {
#IBAction func addContactAction(_ sender: AnyObject) {
alertDialog()
}
let identifier = "contactCell"
var contacts:[String] = [String]()
var managedContext:NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
addContacts(numContacts: 30);
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fetchContacts("") { (list) in
for i in 0..<list.count {
contacts.append(list[i].value(forKey:"name")! as! String)
}
tableView.reloadData()
}
}
func alertDialog() {
//It takes the title and the alert message and prefferred style
let alertController = UIAlertController(title: "Add Contact", message: "", preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.placeholder = "contact"
}
let defaultAction = UIAlertAction(title: "Add", style: .default) { (UIAlertAction) in
let textField = alertController.textFields![0]
self.addContact(name: textField.text!)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
//now we are adding the default action to our alertcontroller
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
//and finally presenting our alert using this method
present(alertController, animated: true, completion: nil)
}
}
extension ContactsTableViewController {
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
// Configure the cell...
cell.textLabel?.text = contacts[indexPath.row]
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let contactToDelete = contacts[indexPath.row]
deleteContact(contactToDelete)
contacts.remove(at: (indexPath as NSIndexPath).row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
}
extension ContactsTableViewController {
func addContacts(numContacts:Int) {
for i in 1..<numContacts {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue("name \(i)", forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
}
self.tableView.reloadData()
}
func addContact(name:String) {
let contact = NSEntityDescription.insertNewObject(forEntityName: "Contact", into: managedContext!) as! Contact
contact.setValue(name, forKeyPath: "name")
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
try managedContext?.save()
print("\(contact.value(forKeyPath: "name") as! String)!) successfully saved")
} catch {
fatalError("Failure to save context: \(error)")
}
self.tableView.reloadData()
}
func fetchContacts(_ predicate:String, completion:(_ array:[Contact]) -> ()) {
var arr:[Contact] = [Contact]()
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Contact")
request.predicate = NSPredicate(format: "name = %#", predicate)
do {
let results = try managedContext?.fetch(request) as! [Contact]
for result in results {
let name = (result as AnyObject).value(forKey: "name") as? String
arr.append(result)
} //for
print(results)
completion(arr as [Contact])
} catch {
print("error fetching results")
} //do
}
func deleteContact(_ name:String) {
fetchContacts(name) { (array) -> () in
for result in array {
let aContact = (result as AnyObject).value(forKey: "name") as? String
if aContact == name {
//delete
self.managedContext?.delete(result)
//save
do {
try self.managedContext!.save()
print("\(aContact) deleted")
} catch {
print("error deleting contact")
} //do
} // if
} //for
}
}
}
My AppDelegate.swift
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded 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.
*/
let container = NSPersistentContainer(name: "ContactLists_coreData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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 parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Generated entity class
import Foundation
import CoreData
extension Contact {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Contact> {
return NSFetchRequest<Contact>(entityName: "Contact");
}
#NSManaged public var name: String?
}
the data model. very simple
<img src="https://drive.google.com/file/d/0B1Usy68B1DzYLUduNTlCY092VEk/view" width="1970" height="1084">
datasource and cell identifier are connected properly
fetchContacts("") always returns an empty list because you have no contacts with a name of "". Also whenever you add or insert to core-data you do not see those changes because you are not doing another fetch and updating the contacts array.
Other more general problems with your code:
You should treat the persistentContainer's viewContext as readonly. To write to core-data use perform​Background​Task(_:​)
after you create the persistentContainer set container.viewContext.automaticallyMergesChangesFromParent = true
use a fetchedResultsController to sync core-data with your view.
If you are doing lots of changes or inserts to core data like you are doing in addContacts do a single save at the end, not after every insert in the loop.

Deleting all data in a Core Data entity in Swift 3

Is there a way to do a batch delete of all data stored in all of the entities in core data?
I read somewhere that in iOS 9 or 10 that apple introduced a way to do batch deletes, but I can't seem to find any good information on it.
Ultimately, I just need a function that goes through an entity, and deletes all of the data in it. Seems like it should be simple enough, but documentation/tutorials for it have proven exceedingly difficult to find.
Any thoughts?
Edit
I added the following code into an IBAction attached to a button:
#IBAction func clearAllData(_ sender: AnyObject) {
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PLProjects")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
//get the data from core data
getPLData()
//reload the table view
tableView.reloadData()
}
This does not seem to work however. If I close down the project and reopen it, the data is still there. I am assuming this is also why the table view doesn't update, because the data is not actually being deleted.
You're thinking of NSBatchDeleteRequest, which was added in iOS 9. Create one like this:
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Employee")
let request = NSBatchDeleteRequest(fetchRequest: fetch)
You can also add a predicate if you only wanted to delete instances that match the predicate. To run the request:
let result = try managedObjectContext.executeRequest(request)
Note that batch requests don't update any of your current app state. If you have managed objects in memory that would be affected by the delete, you need to stop using them immediately.
To flesh out Tom's reply, this is what I added to have a complete routine:
func deleteAllRecords() {
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "CurrentCourse")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try context.execute(deleteRequest)
try context.save()
} catch {
print ("There was an error")
}
}
Declare the Method for getting the Context in your CoreDataManager
Class
class func getContext() -> NSManagedObjectContext {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
}
if #available(iOS 10.0, *) {
return appDelegate.persistentContainer.viewContext
} else {
return appDelegate.managedObjectContext
}
}
Call the above method from your NSManagedObject subClass:
class func deleteAllRecords() {
//getting context from your Core Data Manager Class
let managedContext = CoreDataManager.getContext()
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Your entity name")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try managedContext.execute(deleteRequest)
try managedContext.save()
} catch {
print ("There is an error in deleting records")
}
}

Swift PrepareForSegue ERROR Thread 1: EXC_BREAKPOINT(code=EXC_ARM_BREAKPOINT, subcode=0xdefe

I have really big problems when i try to compile my PrepareForSegue function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "findMap" {
let MapViewController = segue.destinationViewController as UIViewController
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()!
let destinationTitle = filteredDepartments[indexPath.row].name
MapViewController.title = destinationTitle
} else {
let indexPath = self.tableView.indexPathForSelectedRow()!
let destinationTitle = departments[indexPath.row].name
MapViewController.title = destinationTitle
}
}
}
The error opens in the Thread section in the "trap"-row:
--> 0x2f6e18: trap
and the error code is as above:
--> Thread 1: EXC_BREAKPOINT(code=EXC_ARM_BREAKPOINT, subcode=0xdefe
I think the error is in this line:
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
Bur i don't know how to solve it , so please help me ...
It's hard to tell what your code is supposed to mean, but my guess is that you mean this:
if sender === self.searchDisplayController!.searchResultsTableView {
Notice the use of the triple-equals operator to mean "is the same object as".

Resources