How to fetch data with CoreData in the background? - swift

I need to enable my app keep the data for a long time. When I started reading articles on the topic I faced 3 different approaches and realized I do not see any differences. Please explain pros and cons of the following fetch methods:
A)
container.performBackgroundTask { context in
self.data = try! context.fetch(request)
DispatchQueue.main.async {
self.updateUI()
}
}
B)
let context = container.newBackgroundContext()
context.perform {
self.data = try! context.fetch(request)
DispatchQueue.main.async {
self.updateUI()
}
}
C)
let context = container.newBackgroundContext()
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: request) { result in
self.data = result.finalResult!.first!.data!
DispatchQueue.main.async {
self.updateUI()
}
}
try! context.execute(asyncFetchRequest)

I think there is no difference for the example you given(only fetch related).
A) always create a new context, which means it's not safe when multiple running for creating entity or fetch-or-create entity.
In a scenario of creating, you'd better use B), but need to hold the shared background context. when you do 'perform', all jobs running in one queue synchronously.
For C), NSAsynchronousFetchRequest shines when it works with viewContext, you don't have to create a child/background context. but it's not wrong to create one for it.

Related

How to present JSON array in UIPickerView in alphabetical order?

I have a UIPickerView that gets data from JSON and presents it in two columns, one that shows two columns, producer and product using the following:
if let url = URL(string: "https://www.example.com/example"),
let data = try? Data(contentsOf: url),
let tmpValues = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String:String]] {
let tempCategories = tmpValues?.reduce(into: [String:[String]](), { (dict, value) in
if let producer = value["producer"], let product = value["product"] {
dict[producer, default:[]].append(product)
}
})
for category in (tempCategories ?? [:]) {
allCategories.append(Category(name: category.key, items: category.value))
}
pickerView.reloadAllComponents()
}
The issue is while the JSON presents the array in alphabetical order, the PickerView presents the array in random orders every time it is opened, how can this be fixed.
First of all you are strongly discouraged from loading data from a remote URL with synchronous Data(contentsOf. From the documentation
Important
Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.
Secondly, a dictionary is unordered. You could sort the keys and populate the picker source array this way
if let categories = tempCategories {
let sortedKeys = categories.keys.sorted()
allCategories = sortedKeys.map{ Category(name: $0, items: categories[$0]!) }
}
So, before calling:
pickerView.reloadAllComponents()
you can just sort the array
allCategories = allCategories.sorted { $0.name < $1.name }
and it will solve you issue

Reading Data from Realm Database (Swift)

I am new to Realm DataBase and I need a way to read data from realmCloud, but from two different app projects. The way I have tried to implement this is by using query-synced realm. At the moment I'm using a singe realm user to write the data in one app, and the same realm user to read data from another app. The problem is that making a query from the second app(the one used for reading) doesn't return any realm objects ( I have also noticed that user identifier is different from the first one, and also the user permissions are nil.
I have tried setting permissions directly from RealmStudio since documentation is not precise on how to set them from code
func openRealm() {
do {
realm = try Realm(configuration: SyncUser.current!.configuration())
let queryResults = realm.objects(*className*.self)
let syncSubscription = queryResults.subscribe()
let notificationToken = queryResults.observe() { [weak self] (changes) in
switch (changes) {
case .initial: print(queryResults)
case .error(let error): print(error)
default: print("default")
}
}
for token in queryResults {
print(token.tokenString)
}
syncSubscription.unsubscribe()
notificationToken.invalidate()
} catch {
print(error)
}
}
This function prints the data in one app project, but used in another app project with the same user logged in, and the same classFile referenced in the project, it does not. (note that SyncUser.current.identifier is different also
There are a couple of issues.
Some of these calls are asynchronous and the code in your question is going out of scope before the data is sync'd (retreived). The bottom line is code is faster than the internet and you need to design the flow of the app around async calls; don't try to work with the data until it's available.
For example
let notificationToken = queryResults.observe() { [weak self] (changes) in
//here is where results are fully populated
}
// this code may run before results are populated //
for token in queryResults {
print(token.tokenString)
}
Also, let notificationToken is a local var and goes out of scope before the results are populated as well.
These issues are super easy to fix. First is to keep the notification token alive while waiting for results to be populated and the second is to work with the results inside the closure, as that's when they are valid.
var notificationToken: NotificationToken? = nil //a class var
func openRealm() {
do {
let config = SyncUser.current?.configuration()
let realm = try Realm(configuration: config!)
let queryResults = realm.objects(Project.self)
let syncSubscription = queryResults.subscribe(named: "my-projects")
self.notificationToken = queryResults.observe() { changes in
switch changes {
case .initial:
print("notification: initial results are populated")
queryResults.forEach { print($0) }
case .update(_, let deletions, let insertions, let modifications):
print("notification: results, inserted, deleteed or modified")
insertions.forEach { print($0) } //or mods or dels
case .error(let error):
fatalError("\(error)")
}
}
} catch {
print(error)
}
}
deinit {
self.notificationToken?.invalidate()
}
The other advantage of keeping that token (and its corresponding code) alive is when there are further changes, your app will be notified. So if another project is added for example, the code in the 'changes' section will run and display that change.

Multi-threaded core data sometimes returns nil properties

I am new to core data. I have an app that uses core data as local store. Writing to and reading from core data is done by background threads. While this works generally, in rare cases are the fetched data wrong, i.e. properties of a fetched entity are nil.
To check the situation, I wrote a unit test that starts 2 async threads: One fetches continuously from core data, and the other one overwrites continuously these data by first deleting all data, and then storing new data.
This test pretty quickly provokes the error, but I have no idea why. Of course I guess this is a multi-threading problem, but I don’t see why, because fetches and deletion+writes are done in separate managed contexts of a single persistentContainer.
I am sorry that the code below is pretty long, although shortened, but I think without it one cannot identify the problem.
Any help is highly welcome!
Here is my function to fetch data:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try managedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
let nextname = nextCdShoppingItem.name! // Here, sometimes name is nil
} // performAndWait
} // for all cdShoppingItems
completion(nil, nil)
return
} catch let error as NSError {
// error handling
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
I have commented the line that sometimes crashes the test, since name is nil.
Here are my functions to store data:
func overwriteCD(shoppingItems: Set<ShoppingItem>,completion: #escaping () -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: "CDShoppingItem",in: managedContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity,insertInto: managedContext)
nextCdShoppingItem.name = nextShoppingItem.name
} // for all shopping items
self.saveManagedContext(managedContext: managedContext)
completion()
} // performBackgroundTask
} // overwriteCD
func deleteAllCDRecords(managedContext: NSManagedObjectContext, in entity: String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try managedContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey: objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as [AnyHashable: Any], into: [managedContext])
} catch let error as NSError {
// error handling
}
} // deleteAllCDRecords
func saveManagedContext(managedContext: NSManagedObjectContext) {
if !managedContext.hasChanges { return }
do {
try managedContext.save()
} catch let error as NSError {
// error handling
}
} // saveManagedContext
Are you sure that name isn't nil for all requested entities? Just use guard-let to avoid ! for optional variables. Also ! it isn't safe way to unwrap optional variable especially if you can't be sure for source of data.
The problem with my code was apparently a race condition:
While the „fetch“ thread fetched the core data records, and tried to assign the attributes to the properties, the „store“ thread deleted the records.
This apparently released the attribute objects, so that nil was stored as property.
I thought that the persistentContainer would automatically prevent this, but it does not.
The solution is to execute both background threads of the persistentContainer in a concurrent serial queue, the „fetch“ thread synchronously, and the „store“ thread asynchronously with a barrier.
So, concurrent fetches can be executed, while a store waits until all current fetches are finished.
The concurrent serial queue is defined as
let localStoreQueue = DispatchQueue(label: "com.xxx.yyy.LocalStore.localStoreQueue",
attributes: .concurrent)
EDIT:
In the following fetch and store functions, I moved the core data function persistentContainer.performBackgroundTask inside the localStoreQueue. If it were outside as in my original answer, the store code in localStoreQueue.async(flags: .barrier) would setup a new thread and thus use managedContext in another thread that it was created in, which is a core data multi-threading error.
The „fetch“ thread is modified as
localStoreQueue.sync {
self.persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
//…
} // performBackgroundTask
} // localStoreQueue.sync
and the „store“ thread as
localStoreQueue.async(flags: .barrier) {
self.persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
//…
} // performBackgroundTask
} // localStoreQueue.async

cancel filter and sorting of big data array

I'm building vocabulary app using realm. I have several objects of Vocabulary, which contains list of words. One vocabulary contains 45000 words
UI is build such way, that user can search by "BEGINSWITH", "CONTAINS" or "ENDSWITH" through word's title, if corresponding tab is selected.
As, there are several vocabularies, there are some words, that appear in several vocabularies, and I need to remove "duplicates" from UI.
When I do this filtering duplicates on resulted objects + sorting them alphabetically the UI of app freezes, till process completes.
My question is:
1) How can I cancel previous filter and realm filtering request, if tab changed (for example from Contains to Ends"?
2) How can I do all these filter/sorting requests in background, so UI will not freeze?
My code:
let vocabularyPredicate = NSPredicate(format: "enabled == 1 AND lang_from CONTAINS[c] %#", self.language.value)
self.vocabularies = Array(realm.objects(Vocabulary.self).filter(vocabularyPredicate).sorted(byKeyPath: "display_order"))
let result = List<Word>()
for object in self.vocabularies {
let predicate = NSPredicate(format: "title \(selectedFilter.value)[c] %#", self.query.value.lowercased())
result.append(objectsIn: object.words.filter(predicate))
}
self.words = Array(result).unique{$0.title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
selectedFilter.value is selected tab value: "BEGINSWITH", "CONTAINS" or "ENDSWITH"
self.query.value.lowercased() - search query.
unique{$0.title} is extension method for array
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
Actually, realm search is pretty fast, but because of looping through vocabularies and filtering duplicates + sorting alphabetically operations through array of objects - request is freezing for 1-2 seconds.
UPDATE, based on EpicPandaForce and Manuel advices:
I have lurked one more time, and it appeared, that .distinct(by: [keypath]) is already presented in Results in new version of RealmSwift.
I have changed filter/sorting request to
realm.objects(Word.self).filter(vocabularyPredicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
works better know, but I want to ensure, UI will not freeze anyway, by passing objects bettween background thread and UI thread. I have updated adviced construction to:
DispatchQueue.global(qos: .background).async {
let realm = try! Realm()
let cachedWords = CashedWords()
let predicate = NSPredicate(format: "enabled == 1")
let results = realm.objects(Word.self).filter(predicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
cachedWords.words.append(objectsIn: results)
try! realm.write {
realm.add(cachedWords)
}
let wordsRef = ThreadSafeReference(to: cachedWords)
DispatchQueue.main.async {
let realm = try! Realm()
guard let wordsResult = realm.resolve(wordsRef) else {
return
}
self.words = Array(wordsResult.words)
if ((self.view.window) != nil) {
self.tableView.reloadData()
}
}
print("data reload finalized")
}
1) How can I cancel previous filter and realm filtering request, if tab changed (for example from Contains to Ends"?
You could create an NSOperation to perform the task and check if it's been cancelled between each of the steps (fetch, check isCancelled, filter, check isCancelled, sort). You won't get to cancel it immediately, but it could improve your performance. It also depends on which of those three steps (fetch, filter, sort) is taking longer...
2) How can I do all these filter/sorting requests in background, so UI will not freeze?
You could run that operation inside a new NSOperationQueue.
Or just use GCD, dispatch a block to a background queue, create a Realm instance in the block and run your code there, then dispatch the results back to the main queue to update the UI.
Something like this:
DispatchQueue.global(qos: .userInitiated).async {
guard let realm = try? Realm() else {
return // maybe pass an empty array back to the main queue?
}
// ...
// your code here
// ...
let words = Array(result).unique{$0.title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
// Can't pass Realm objects directly across threads
let wordReferences = words.map { ThreadSafeReference(to: $0) }
DispatchQueue.main.async {
// Resolve references on main thread
let realm = try! Realm()
let mainThreadWords = wordReferences.flatMap { realm.resolve($0) }
// Do something with words
self.words = mainThreadWords
}
}
Additionally, you should try to optimize your query:
let predicate = NSPredicate(format: "vocabulary.enabled == 1 AND vocabulary.lang_from CONTAINS[c] %# AND title \(selectedFilter.value)[c] %#", self.language.value, self.query.value.lowercased())
let words = realm.objects(Word.self).filter(predicate).sorted(byKeyPath: "title")
let wordsReference = ThreadSafeReference(words)
// resolve this wordsReference in the main thread

Append multiple VNCoreMLModel ARKit and CoreML

I'm a noob and I don't really know how can I happened multiple CoreML model to the VNCoreMLRequest.
With the code below is just using one model but I want to append also another model (visionModel2 on the example below). Can anyone help me? Thank you!
private func performVisionRequest(pixelBuffer: CVPixelBuffer){
let visionModel = try! VNCoreMLModel(for: self.iFaceModel.model)
let visionModel2 = try! VNCoreMLModel(for: self.ageModel.model)
let request = VNCoreMLRequest(model: visionModel){ request, error in
if error != nil {
return
}
guard let observations = request.results else {
return
}
let observation = observations.first as! VNClassificationObservation
print("Name \(observation.identifier) and confidence is \(observation.confidence)")
DispatchQueue.main.async {
if observation.confidence.isLess(than: 0.04) {
self.displayPredictions(text: "Not recognized")
print("Hidden")
}else {
self.displayPredictions(text: observation.identifier)
}
}
}
To evaluate an image using multiple ML models, you’ll need to perform multiple requests. For example:
let faceModelRequest = VNCoreMLRequest(model: visionModel)
let ageModelRequest = VNCoreMLRequest(model: visionModel2)
let handler = VNImageRequestHandler( /* my image and options */ )
handler.perform([faceModelRequest, ageModelRequest])
guard let faceResults = faceModelRequest.results as? [VNClassificationObservation],
let ageResults = ageModelRequest.results as? [VNClassificationObservation]
else { /*handle errors from each request */ }
(Yes, you can run Vision requests without a completion handler and then collect the results from multiple requests. Might want to check prefersBackgroundProcessing on the requests and dispatch everything to a background queue yourself, though.)
After that, you probably want to iterate the results from both requests together. Here’s a handy way you could do that with Swift standard library sequence functions, but it assumes that both models return information about the same faces in the same order:
for (faceObservation, ageObservation) in zip (faceResults, ageResults) {
print(“face \(faceObservation.classification) confidence \(faceObservation.confidence)”)
print(“age \(ageObservation.classification) confidence \(ageObservation.confidence)”)
// whatever else you want to do with results...
}
Disclaimer: Code written in StackExchange iOS app, not tested. But it’s at least a sketch of what you’re probably looking for — tweak as needed.