NSPersistentContainer, performBackgroundTask, calling perform does nothing - core-data

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.

Related

Kotlin Coroutines : Waiting for multiple threads to finish

So looking at Coroutines for the first time, I want to process a load of data in parallel and wait for it to finish. I been looking around and seen RunBlocking and Await etc but not sure how to use it.
I so far have
val jobs = mutableListOf<Job>()
jobs += GlobalScope.launch { processPages(urls, collection) }
jobs += GlobalScope.launch { processPages(urls, collection2) }
jobs += GlobalScope.launch { processPages(urls, collection3) }
I then want to know/wait for these to finish
You don't need to manually keep track of your cuncurrent jobs if you use the concept of structured concurrency. Assuming that your processPages function performs some kind of blocking IO, you can encapsulate your code into the following suspending function, which executes your code in an IO dispatcher designed for this kind of work:
suspend fun processAllPages() = withContext(Dispatchers.IO) {
// withContext waits for all children coroutines
launch { processPages(urls, collection) }
launch { processPages(urls, collection2) }
launch { processPages(urls, collection3) }
}
Now, from if a topmost function of your application is not already a suspending function, then you can use runBlocking to call processAllPages:
runBlocking {
processAllPages()
}
You can use async builder function to process a load of data in parallel:
class Presenter {
private var job: Job = Job()
private var scope = CoroutineScope(Dispatchers.Main + job) // creating the scope to run the coroutine. It consists of Dispatchers.Main (coroutine will run in the Main context) and job to handle the cancellation of the coroutine.
fun runInParallel() {
scope.launch { // launch a coroutine
// runs in parallel
val deferredList = listOf(
scope.asyncIO { processPages(urls, collection) },
scope.asyncIO { processPages(urls, collection2) },
scope.asyncIO { processPages(urls, collection3) }
)
deferredList.awaitAll() // wait for all data to be processed without blocking the UI thread
// do some stuff after data has been processed, for example update UI
}
}
private fun processPages(...) {...}
fun cancel() {
job.cancel() // invoke it to cancel the job when you don't need it to execute. For example when UI changed and you don't need to process data
}
}
Extension function asyncIO:
fun <T> CoroutineScope.asyncIO(ioFun: () -> T) = async(Dispatchers.IO) { ioFun() } // CoroutineDispatcher - runs and schedules coroutines
GlobalScope.launch is not recommended to use unless you want the coroutine to be operating on the whole application lifetime and not cancelled prematurely.
Edit: as mentioned by Roman Elizarov you can try not to use awaitAll() function unless you want to update UI or do something else right away after all data are processed.
Following approach can be used.
fun myTask() {
GlobalScope.launch {
val task = listOf(
async {
},
async {
}
)
task.awaitAll()
}
}

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

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
}
}
}

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.

How can these sync methods be effectively unit tested?

Based on answers to this question, I feel happy with the simplicity and ease of use of the following two methods for synchronization:
func synchronized(lock: AnyObject, closure: () -> Void) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
func synchronized<T>(lock: AnyObject, closure: () -> T) -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
But to be sure they're actually doing what I want, I want to wrap these in piles of unit tests. How can I write unit tests that will effectively test these methods (and show they are actually synchronizing the code)?
Ideally, I'd also like these unit tests to be as simple and as clear as possible. Presumably, this test should be code that, if run outside the synchronization block, would give one set of results, but give an entirely separate set of results inside these synchronized blocks.
Here is a runnable XCTest that verifies the synchronization. If you synchronize delayedAddToArray, it will work, otherwise it will fail.
class DelayedArray:NSObject {
func synchronized(lock: AnyObject, closure: () -> Void) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
private var array = [String]()
func delayedAddToArray(expectation:XCTestExpectation) {
synchronized(self) {
let arrayCount = self.array.count
self.array.append("hi")
sleep(5)
XCTAssert(self.array.count == arrayCount + 1)
expectation.fulfill()
}
}
}
func testExample() {
let expectation = self.expectationWithDescription("desc")
let expectation2 = self.expectationWithDescription("desc2")
let delayedArray:DelayedArray = DelayedArray()
// This is an example of a functional test case.
let thread = NSThread(target: delayedArray, selector: "delayedAddToArray:", object: expectation)
let secondThread = NSThread(target: delayedArray, selector: "delayedAddToArray:", object: expectation2)
thread.start()
sleep(1)
secondThread.start()
self.waitForExpectationsWithTimeout(15, handler: nil)
}

How to present a view controller while performing background tasks?

I am using Parse.com and swift
I have an initial view controller that presents the parse.com login.
Once the login is complete and the objects are saved in the background I want to present my navigation controller's root view controller (first controller linked in the storyboard).
How is this done with all the asynchronous calls?
This is what I have but it jumps back to the login screen and doesn't
func signUpViewController(signUpController: PFSignUpViewController!, didSignUpUser user: PFUser!) {
currentUser = user as? User
currentUser!.isManager = false
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if objects.count == 1 {
currentUser!.isManager = true
}
currentUser?.saveInBackgroundWithBlock({ (success: Bool, error: NSError!) -> Void in
if success == false || error != nil {
println(error)
} else {
currentCompany = Company()
currentCompany!.companyName = "My Company"
currentCompany!.saveInBackgroundWithBlock({ (success: Bool!, error: NSError!) -> Void in
if success == false {
println(error)
}
})
}
})
}
dismissViewControllerAnimated(true, completion: { () -> Void in
self.performSegueWithIdentifier("jumpFromInitialToMessages", sender: self)
// let vc = MessagesViewController()
// self.navigationController?.presentViewController(vc, animated: true, completion: nil)
})
}
If you want to initiate a UI transition/update inside the completion handler, you should insert a dispatch back to the main queue inside the completion handler closure:
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
// do something
// when done, do some UI update, e.g. perform segue to another scene
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("identifier", sender: self)
}
}
This is an example with a simple query, but the idea works with any asynchronously called completion handler closure: Just dispatch the UI update back to the main queue from within the closure.

Resources