iOS Core Data Fetch Request, how to use - ios

I am using Core Data to see whether messages in a table view have been seen before by the user. The way I do this is to save the message Id to Core Data the first time it is seen, and then I run a fetch request when I update the table view to see if there is an entry in the persistent memory with the same Id.
Now what I want to know is how I should most effectively implement my fetch request, based on how time consuming it is. Should I either run a request that returns all saved message Ids as an array when the view is loaded, and then in cellForRowAtIndexPathcheck if that array contains that cell's message Id, or run the fetch request with a predicate in cellForRowAtIndexPath? The latter would be my preferred method, but If i have 100 or so cells I wondered if this would be poor etiquette.
Any help would be very much appreciated.
This is my fetch Request :
func persistQuery(predicateValueString: String!) -> Bool! {
let fetchRequest = NSFetchRequest(entityName: "LogItem")
let predicate = NSPredicate(format: "itemText == %#", predicateValueString)
fetchRequest.predicate = predicate
var didFindResult = true
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
if fetchResults.count == 0 {
didFindResult=false
}
}
return didFindResult
}

The best way is to use a NSFetchedResultsController. It will optimize the fetching and the memory footprint as well. It is specifically designed for table views.
To get started, take a look at the Xcode template (Master/Detail, check Core Data). It is really quite simple.
Make sure you also implement the delegate methods - they will automatically be called when your managed objects change, so there is only minimal code that is executed to update the UI (an only if the object is actually on screen).
Presumably each of your table view cells represent a LogItem (the NSManagedObject subclass) with a property to indicate the read status. Once you change that, the delegate method will try to update it based on the index path.
That's all there is to it. With the fetched results controller you get a lot of optimization for free, so I would strongly recommend using it whenever you populate a table view with Core Data entities.

Related

AWS DynamoDB queries using Swift

I am using swift and AWS DynamoDB for mobile app. I followed the tutorial and can save data successfully. However , when I try to load data , i found I the saving and loading data always come after all tasks in the viewdidload finished, so I can not pass the data out in the same view? Is there any way to save or retire data immediately ?
below is my code
mapper.query(Table.self, expression: queryExpress).continueWith{(task: AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
print("test")
if let error = task.error as NSError? {
print("The requst failed. Error: \(error)")
}
if let paginatedOutput = task.result {
for item in paginatedOutput.items
{
print("quring")
//pass info out to array
}
}
return nil
}
Fetching data from the network is an asynchronous action. You can't delay loading the screen while it completes. It may take a long time. It might not ever complete.
Your view controller must handle the case that it doesn't have data yet, and update itself when that data becomes available. The first step to this is avoiding making network queries in your view controller. View controllers should never directly query the network. They should query model objects that outlive the view controller. The model objects are responsible for making queries to the network and updating themselves with the results. Then the view controller will update itself based on the model. The name for this pattern is Model View Controller and is fundamental to Cocoa development. (Search around for many tutorials and discussions of this pattern.)
But regardless of where you make the queries and store the data, you will always have to deal with the case where the data is not yet available, and display something in the meantime. Nothing can fix this in a distributed system.
When the query finishes successful, load the data into your view. You can send the query in your viewDidLoad method, but you need to present the data when it arrives using another method you call when the data did arrive.

Implement UITableViewDataSourcePrefetching with an NSFetchedResultsController data source

I have a UITableView that gets data from an NSFetchedResultsController. To this end, i basically copied Apples example implementation from https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html.
Now at WWDC16 Apple announced the UITableViewDataSourcePrefetching protocol, which provides callbacks that let you prefetch data so it's already loaded when it's needed to be displayed by the tableview. I'm looking for an example on how to integrate this with the NSFetchedResultsController, because i can't figure out if i'm doing this correctly.
Should i simply create a dictionary, as an in-memory cache, to hold prefetched data and be used in cellForRowAtIndexPath instead of querying fetchedResultsController.object(at: indexPath) directly?
As i understand it, CoreData already caches fetched data automatically, so maybe i just need to call fetchedResultsController.object(at: indexPath) in the prefetch callbacks, to ensure the data gets cached?
Or shouldn't i prefetch data at all when using a fetched results controller, because i would work around the magical integration provided by Apple?
Or something else entirely?
*edit*: I found a slide from the Core Data talk at WWDC16 that supposedly explains this, but i understand it at all.
An async fetch request? I thought they don't work with NSFetchedResultsController. I guess that's why it's performed on the managedObjectContext directly?
The async fetch request is created from the results of calling .performFetch() on the NSFetchedResultsController. Nothings showing up until i call that. But since all the results are there after calling it, i don't get why i would need to prefetch them again.
It will improve performance if your model has faults. Whether your models have faults or not depends on iOS optimization, and how you create your schema and relationships.
So to be safe your could add the prefetch code. I have the code for Swift 4.
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let fetchRequest: NSFetchRequest<MyModel> = MyModel.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
let items = indexPaths.map { fetchedResultsController.object(at: $0) }
fetchRequest.predicate = NSPredicate(format: "SELF IN %#", items)
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest)
do {
try fetchedResultsController.managedObjectContext.execute(asyncFetchRequest)
} catch { }
}
What it does is pretty self explanatory. It runs an async fetch request on the managed object context, therefore resolving faults, if any.
In short, you are creating a custom predicate to "warm" the predicted specific objects in the MOC. The idea, I think, is that the table view is telling you more targeted scroll target info (based on velocity etc.) about where it thinks the table view will end up. This allows you to execute an async request to pre-fetch these objects in the MOC so that by the time you are actually asking for properties on it, they're ready to go.
This is why you also have a nil completion on the async request. You aren't directly using the results because you may not actually be displaying any of these index paths yet. It also means it's unnecessary to do additional tracking (like in a Dictionary, etc.) of results when you get pre-fetched objects.
The real question is, if you are using an FRC with batching, pre-fetched properties, etc., is this really buying you anything to side-step it? I'm not really sure. I haven't noticed a huge difference between a pretty highly tuned FRC+UITableView with vs without pre-fetching on a table of around 1000 results. It's also possible by merely touching those objects that the FRC will see that (because you are sharing the same MOC) and then will run through it's property/relationship pre-fetching, etc. I haven't seen much documentation on this mechanism, but if Apple is suggesting it, especially using an FRC, I have to think it helps.

NSFetchedResultsController notifies its Delegate of delete changes when a managed object is modified, and never notifies for Insert or Update

I have a UITableViewController, which is a delegate for an NSFetchedResultsController. My NSFetchedResultsControllerDelegate functions are set up as per "Typical Use" in Apple's documentation, with the table view controller as the fetched result controller's delegate property.
I also have some view controllers which are presented on top of the table view where the managed objects can be modified. These modifications are done on the same managed object context. There is only one managed object context constructed in my application, and it is accessed globally. (I have put some print statements in my object context construction just to be sure I am not accidentally re-constructing it elsewhere.)
When I modify one of the managed objects, the delegate function controller:didChangeObject is called, but the NSFetchedResultsChangeType is always .Delete. When I create a managed object, the delegate function does not fire at all.
However, when I manually call call performFetch() and tableView.reloadData(), the cells are restored to the correct state: the removed row comes back, any not-inserted rows are created.
The result is that deleting an object works as expected (the cell is removed), but updates to an object cause its cell to be removed, and object creations do not trigger cell inserts.
I tried to create a simple demo of this behaviour, but when I re-created the situation from a blank application, I don't see this behaviour. So something within my application is causing this strange behaviour, but I can't figure out what. Any ideas?
Extra Info:
The actual construction of the predicate and sort descriptors are done over several different classes, but printing them via print(resultsController.fetchRequest.predicate) and print(resultsController.fetchRequest.sortDescriptors) gives the following:
Optional(readState == "2" OR readState == "1")
Optional([(readState, ascending, compare:), (title, ascending, compare:)])
I have put a print statement in my controller:didChangeObject: method, and I can see that this only gets called with type.rawValue = 2 (i.e. .Delete), and only when I modify objects, not when I create them.
It's an inconsistency with how NSFetchedResultsController handles its NSPredicate.
If the NSFetchedResultsController is constructed with a fetch request which has a predicate which does a comparison between an integer and a string like follows:
let predicate = NSPredicate(format: "integerAttribute == %#", String(1))
this will lead to the predicate string being:
integerAttribute == "1"
When this is the case, initial fetches work fine: calling the function performFetch() on the fetched results controller returns all objects where the integerAttribute is equal to 1 (where integerAttribute is of type Int32).
However, the notifications to NSFetchedResultsControllerDelegate do not work fine. Modifications of managed objects result in the delegate being notified of a NSFetchedResultsChangeType.Delete change. Creations of managed objects do not invoke the delegate at all.
To make all this weirdness go away, fix the predicate format string as follows:
let predicate = NSPredicate(format: "integerAttribute == %d", 1)

Connecting remote search results with local database using CoreData

Assume we have simple data model with single entity User; simple tableView_friends with fetchedResultsController_friends for show users - friends.
Assume we have search bar for searching all (not only friends) users in service, and for every typed in it character we perform search request to server, which return to us somehow filtered by character User objects. Some of this objects can already be inside local database. By app logic we don't really must save all this results in local database forever (but ok, we can, we can clear local database time to time); on other hand, if we will perform any action on some searched user, we must store this user. We want to show list of searched user in other tableView_search with fetchedResultsController_search.
Question: should I use same context for fetchedResultsController_friends and fetchedResultsController_search? If no, how can I handle situation, when I wish to edit searched user, which already exists in database and probably already local edited? If yes, how can I setup predicate for fetchedResultsController_search (server perform its own logic for search by character, which can be changed) for show exactly same result as from server?
We recently implemented a search feature in our application and had a similar issue, We had local data in core data and also remote data from our API.
You have a few options that we explored:
Save your data into core data from the API as it is retreived and
then the fetched results controller will do the rest
Manage the merge of the data yourself, you can still use NSFetchedResults controller to an extent but need to do more work
We didn't want to save all of the information returned from the API unless it was needed (the user selected it), so we come up with a simple solution that worked for our app. This may not work directly for your app, you may need a completely different solution or change some of the things we done to suit.
Firstly, To explain what we are dealing with, we had a Article entity in core data which contains around 25 properties, the API returns article objects as JSON data with the same data.
What we decided to do was to create a class which represents a simple version of an article (just enough data to show in a list view and reference it later in the API or core data) which looked something like this:
class SearchResult: NSObject {
var id:String?
var title:String?
var imageUrl:String?
var url:String?
// core data entity
init(article:Article) {
self.id = content.contentId
self.title = content.title
self.featuredImageURL = content.absoluteImagePath()
self.urlAlias = content.urlAlias
self.publishedAt = content.publishedAt
}
init(articleDictionary:NSDictionary) {
self.id = articleDictionary.objectForKeyNotNull("id") as? String
self.title = articleDictionary.objectForKeyNotNull("title") as? String
self.url = articleDictionary.objectForKeyNotNull("url") as? String
if let imageUrl = articleDictionary.objectForKeyNotNull("imageUrl") as? String {
self.imageUrl = imageUrl
}
}
}
Now using this, we can create once of these from either the core data results or from the API results. Our tableview datasource is just an array
var dataSet = [SearchResult]()
We use the NSFectchResultsController delegate methods to add/remove/re-order core data elements from the dataSet after the initial load and when we get API data we'll do something like:
dataSet = Array(Set(apiResponseArray + dataSet))
This will take an array of SearchResult items from the API, merge them with the current result set and remove duplicates. casting to a set and then back to an array will give you an array of unique results as a Set is made of unique values only.
See this reference which should help with how the delegate methods would work

Asynchronous Fetching Swift Xcode

I use a collection view to display images stored in CoreData, only a few (around 15) but when I scroll down it lags, It seams to be because I am not fetching my data asyncrhronously, is it possible with only 15 UIImages?
So here is my problem I cannot find a descent tutorial on asynchronous fetching in swift anywhere ,3 days I am looking. Sorry if I didn't search well.
This is what arrived to do with some bits from tutorials
let entity = NSEntityDescription.entityForName("Dreams", inManagedObjectContext: contxt)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
let asyncFetch = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) {
(result:NSAsynchronousFetchResult!) -> Void in
if result.finalResult!.count > 0 {
self.dreams = result.finalResult! as [Dream]
}
NSLog("Done.")
}
Dreams is an array of type [Dream] but when I display the collection view nothing appears
This is how I did it previously
let fetchRequest = NSFetchRequest(entityName: "Dreams")
if let fetchResults = contxt.executeFetchRequest(fetchRequest, error: nil) as? [Dream] {
dreams = fetchResults
}
It worked very well,
Thank you very much for your help
Unfortunately, I don't have time to offer specific code in this instance. I did want to present a basic strategy, however. Fetching on the main thread is exceedingly unlikely to be your problem here, but fetching everything at once definitely could be, particularly with large binary data involved. This should be sped up significantly if:
You start using an NSFetchedResultsController with a good batch size rather than a raw fetch request. NSFetchedResultsController is a little finicky about updates with collection views, in ways which are beyond the specific scope of this question, but they work very well once set up correctly.
You store the images as files instead of in Core Data itself, and store the path to the file instead. Then that image data won't get loaded into memory every time you fault in the object, and you can simply load and display the images as the cells come on screen using that file URL.
People who insist on storing binary data in Core Data usually find that storing the data as the only attribute in a separate "Image" entity with a relationship to the entity you are displaying helps. The image object (along with its binary image data) can then be kept a fault, and thus not in memory, even when the object you fetch for the collection view is in memory. I, however, prefer not to store them in Core Data, and simply handle the resulting atomicity gotchas and the possibility the file Core Data points to got deleted somehow.

Resources