I'm looking at the Firechat demo project, and they use a technique whereas they're using FEventTypeChildAdded as well as FEventTypeValue as the latter will fire just after FEventTypeChildAdded. This is done in order to prevent a "spam" of FEventTypeChildAdded callbacks to avoid a UI lockup. However, I implemented the same technique in my app, but FEventTypeValue is actually called before FEventTypeChildAdded, resulting in the BOOL changing to NO and locking the UI.
From the demo project:
// This allows us to check if these were messages already stored on the server
// when we booted up (YES) or if they are new messages since we've started the app.
// This is so that we can batch together the initial messages' reloadData for a perf gain.
__block BOOL initialAdds = YES;
[self.firebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
// Add the chat message to the array.
if (newMessagesOnTop) {
[self.chat insertObject:snapshot.value atIndex:0];
} else {
[self.chat addObject:snapshot.value];
}
// Reload the table view so the new message will show up.
if (!initialAdds) {
[self.tableView reloadData];
}
}];
// Value event fires right after we get the events already stored in the Firebase repo.
// We've gotten the initial messages stored on the server, and we want to run reloadData on the batch.
// Also set initialAdds=NO so that we'll reload after each additional childAdded event.
[self.firebase observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
// Reload the table view so that the intial messages show up
[self.tableView reloadData];
initialAdds = NO;
}];
Related
I am going to build lazy loading functionality which has dynamic size records in database, so i am doing this in following manner
Initially i fetch say 20 records ,
Then i process one,two, or any number of records based on user's clicks (I am not holding user to wait for finish some service call or pending task, so user can do anything with the list like scroll or click etc.)
Now to maintain consistency i am fetching few records first and then process users record, here i am doing so because the records which i processed are removed from the actual list of records, so here the records at Database and app side are not fixed list.
I am doing right now with following ways :
-(void)viewDidLoad {
// Here i fetch first 20 records and then show in UITableView
// After user see the list he can click on any button to process that records
// And i am trying to queue that service call and maintain consistency in records
}
-(void)processOneRecord {
dispatch_queue_t queue = dispatch_queue_create("com.developer.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// To get further records service call
[self getMoreRecordsCompletionBlock:^{
[self ProcessOneRecordServiceCall];
}];
});
}
Here i am creating a method processOneRecord which calls every time user clicks on button on cell
In processOneRecord method i create completion block for first service call, so second service call definitely run after first service all response. But now when user click on button on another cell i want to wait that first service call until second service call response return to application.
Thanks for the response guys.
I have achieved desired functionality with help of Rob's answer, I have created one function in which i write one NSBlockOperation which later i added to NSOperaiotnQueue. And every time when this function calls i cheated whether previously added operation is finished, if yes then add another operation else it will wait previous operation until its completion.
In below code snippet below variable are global to my current UIViewController and also they are initialised in my UIViewController's ViewDidLoad and other respective methods.
Bool isRecordProcessed, isLoadMoreCallFinihsed;
NSOperationQueue *operationQueue;
-(void)callTwoAPIServiceCalls {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// To get further records first
[self getMoreRecordsCompletionBlock:^{
[self ProcessOneRecordServiceCall];
}];
}];
if (operationQueue.operations.count == 0 && isRecordProcessed && isLoadMoreCallFinihsed) {
[operationQueue addOperation:operation];
}
else {
[self performSelector:#selector(callTwoServiceCalls:) withObject:nil afterDelay:0.1];
}
}
I am using Realm for my app, i want to be able to query results on a background thread and receive them on the main thread.
What is the best way to achieve this? and what is the best practice to use realm (having different method for main thread and background? and in main using a static instance of realm across the app? maybe another good way?)
I've read and saw this options are available:
- parsing the realm object to my own object and return them (kind of a copy of the results).
- returning a the key of the object and querying again from main thread.
Thanks for any help anyone can give me, i really think realm has great potential but there is a lack of good tutorials and best practices.
First, since Realm is fast enough in most cases, you do not need to run a query in the background.
So the basic strategy is; update in background, fetch in the main thread.
The most general way is to take advantage of the feature of the live update.
RLMResults and Results have live update. You can hold RLMResults/Results instances by query. Then you'll make any changes in background thread, the changes are notified and reflected automatically when committed.
// Hold RLMResults for the data source
self.array = [[DemoObject allObjects] sortedResultsUsingProperty:#"date" ascending:YES];
// Reload table view when changed by other threads
__weak typeof(self) weakSelf = self;
self.notification = [RLMRealm.defaultRealm addNotificationBlock:^(NSString *note, RLMRealm *realm) {
[weakSelf.tableView reloadData];
}];
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// RLMResults is updated automatically
return self.array.count;
}
// Update in background
- (void)backgroundAdd
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Import many items in a background thread
dispatch_async(queue, ^{
// Get new realm and table since we are in a new thread
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
for (NSInteger index = 0; index < 5; index++) {
// Add row via dictionary. Order is ignored.
[DemoObject createInRealm:realm withValue:#{#"title": [self randomString],
#"date": [self randomDate]}];
}
[realm commitWriteTransaction];
});
}
For more details, you can see the table view example in Realm's repo.
If a few cases that Realm doesn't fast enough when fetch on the main thread, you can fetch in background thread. Then aggregate an array of primary keys. Then pass the array and re-fetch on the main thread using the primary keys.
FYI: We are working to add support for running queries asynchronously https://github.com/realm/realm-cocoa/pull/2842
If this feature will be released, you don't need to aggregate primary keys and re-fetch.
I am using a CollectionView which displays an array of objects.
On clicking a button i fill this array with a new set of data.
I want to refresh the CollectionView with this data.
Is there any statement to update this instead of comparing each items and selectively deleting and adding? The reloadData usually ends up in the following error.
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
In Short, I am looking for the following steps...
1)Fill the datasource array, show the data.
2)Fill the datasource array with new data, refresh the CollectionView to show the data.
Thanks in Advance
Try - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion.
In your case, you want "an all new set of data", so to speak, so e.g:
[myCV performBatchUpdates:^{
// one of:
// a)
[myCV deleteSection:someIndexSetForTheEntireSection];
[myRealDataSource empty:someIndexSetForTheEntireSection];
//
// OR b)
[myCV deleteItemsAtIndexPaths:someSetOfIndexPaths];
[myRealDataSource removeIndexPaths:someSetOfIndexPaths];
// Either case:
NSArray *indexPaths = [myRealDataSource getNewDataAndReturnIndexPaths];
// if a)
[myCV insertSections:newIndexSetForNewSection];
// Either case:
[myCV insertItemsAtIndexPaths:newIndexSetForInsertions];
}
completion:^(BOOL finished) {
NSLog(#"Done.");
// Maybe signal something else if you want.
}];
performBatchUpdates:completion: will expect the deletions & insertions from the original data source check entering the function to add up to the correct data source size leaving the method. It will loudly complain otherwise.
If you only have one section (section 0), you can be much more general than specific index paths if you are always "removing everything" and "inserting a complete new set".
Another option to to use KVO to listen on insertions and removals from the data source and simply reloadData, reloadItemsAtIndexPaths: or reloadSections: as appropriate.
I prefer the reactive KVO version, as I tend to use collection views with Core Data, and not pseudo-static or in-memory NSArray's.
To figure out the CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION issue, I'd setup a breakpoint on all exceptions, and try to discover what is really triggering the issue. Likely your datasource is gone and there's a bad access when you try to read/write from it.
Suppose you arrive on your view then you can add data to your array in viewDidLoad method like so:
-(void)viewDidLoad
{
[super viewDidLoad];
// If you have already data
self.arr_sample=(NSArray *)array;
/*
When you want to download data from server than you have to call reloadData
method of collection because all delegate method already called before view load.
So loading data will take time to load data than you have to call all delegate
method of collectionview by calling reloadData method.
*/
[self loadData];
// Do any additional setup after loading the view from its nib.
}
but first of all you have set the delegate of collectionview .
do you want to download data from server than you can call reloaddata method of collection view. such as
-(void)loadData
{
// Do your downloading code her and call to reload data method of collectionview
[collectionview reloadData];
}
now again do you want to refill your array with new data on your button click than you can do
-(void)refillData
{
// Download new data code here and follow
[array removeAllObjects];
array=(NSArray *)newarray;
[collectionview reloadData];
}
I have this Firebase reference to retrieve all the pending (unread) messages by passing the priority of last read message. I do this onload of the chat room.
NSNumber* lastPriority = [self getLastPriority];
__block FirebaseHandle handle = [[msgListRef queryStartingAtPriority:lastPriority] observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
// here I will get all the pending messages and add them to my local message list and reload the UITableView
[self addMessageToList: snapshot]
[msgListRef removeObserverWithHandle:handle];
// Then I call the below function to keep listening for new messages when the chat is in progress
[self addMessageListener];
}];
- (void) addMessageListener{
msgListRef = [[Firebase alloc] initWithUrl:messageListUrl];
NSNumber* lastPriority = [self getLastPriority];
[[msgListRef queryStartingAtPriority:lastPriority] observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
[self addMessageObject:snapshot.value];
// Here I will reload the table view. This one is supposed to fire for each
//message since I used FEventTypeChildAdded.
//*** BUT THIS CALLBACK IS NEVER FIRED ***
}];
}
Any idea why the second callback with FEventTypeChildAdded observerType never gets fired when I already called FEventTypeValue before that? It works if I don't read all with FEventTypeValue. But in that case my UITableView reload will get called for each pending messages when the user enters the chat room.
Sorry guys.The issue was with my code. The firebase reference (msgListRef) was re-initilized at another function that I didnt notice.
I need some help in using objects from Core Data with GCD; I seem to get NSManagedObjects that are aren't faulted into the main thread, even when I access their properties. Would appreciate some help.
This is what I'm doing: on launch, I need to load a list of Persons from the Core Data DB, do some custom processing in the background, then reload the table to show the names. I am following the guidelines for Core Data multi-threading by only passing in the objectIDs into the GCD queues. But when I reload the tableview on the main thread, I never see the name (or other properties) displayed for the contacts, and on closer inspection, the NSManagedObjects turn out to be faults on the main thread, even though I access various properties in cellForRowAtIndexPath. The name property is visible in the background thread when I NSLog it; and it's also showing correctly on the main thread in NSLogs in cellForRowAtIndexPath. But they don't show in the tableView no matter what I do. I tried accessing the name property using the dot notation, as well as valueForKey, but neither worked.
Here's my code …. it's called from the FRC initializer:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
__fetchedResultsController = [self newFetchedResultsControllerWithSearch:nil]; // creates a new FRC
[self filterAllContactsIntoDictionary: __fetchedResultsController];
return [[__fetchedResultsController retain] autorelease];
}
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
NSArray *fetchedIDs = [[frc fetchedObjects] valueForKey:#"objectID"];
NSArray *fetched = [frc fetchedObjects];
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[[self.fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
NSMutableArray *backgroundObjects = [[NSMutableArray alloc] initWithCapacity: fetchedIDs.count];
// load the NSManagedObjects in this background context
for (NSManagedObjectID *personID in fetchedIDs)
{
Person *personInContext = (Person *) [backgroundContext objectWithID: personID];
[backgroundObjects addObject:personInContext];
}
[self internal_filterFetchedContacts: backgroundObjects]; // loads contacts into custom buckets
// done loading contacts into character buckets ... reload tableview on main thread before moving on
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
What am I doing wrong here? Is there any other way to explicitly make the Person objects fire their faults on the main thread? Or am I doing something wrong with GCD queues and Core Data that I'm not aware of?
Thanks.
Why not take the easy route, since you are not saving anything new ?
Instead of creating an extra context for the background thread and working with IDs, use the main managedObjectContext in the background thread after locking it.
for example:
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *context = ... // get the main context.
[context lock]; // lock the context.
// do something with the context as if it were on the main thread.
[context unlock]; // unlock the context.
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
This works for me when I call a method with performSelectorInBackground, so I guess it should work for GCD dispatch too.
Well, mergeChangesFromContextDidSaveNotification: is your friend. You'll need to tell the MOC on the main thread that there have been changes elsewhere. This will do the trick.
Here's Apple's documentation. To quote from there:
This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted.
EDIT: original answer removed, OP is not fetching in the background
I looked closer at your code and it doesn't look like you are doing anything that will change data and/or affect the context on the main thread.
You have a fetchedResultsController on the main thread. Presumably, this is working and your table is populating with data. Is this true?
When filterAllContentsIntoDictionary is invoked, you pass an array of the fetchedResultsController's current objectIDs to a background thread and do some processing on them (presumably filtering them based on some criteria) but you are not changing data and saving backgroundContext.
internalFilterFetchedContents is a black box. Without knowing what you intend for it to do, hard to say why it's not working.
When this is done, you reload the table on the main thread.
You haven't made any changes to the store, the context, or the fetchedResultsController so of course, the table shows the same data it did before. The missing details to help further are:
Is your tableView showing correct data from the fetchedResultsController to begin with? If not, most likely your only problem is in handling the tableView delegate and dataSource methods and the rest of this isn't really relevant.
What do you intend to happen in filterAllContentsIntoDictionary and internalFilterFetchedContents?
If your intent is to filter the data as displayed by the fetchedResultsController not sure you need to do anything in the background. If you modify the fetchRequest and do performFetch again your table will reload based on the new results.
I you need more help, please answer my questions, add more relevant code to your post and let me know if I'm missing anything wrt the problem and what you're trying to accomplish.
good luck!