I am trying to fetch data from Firebase and after that update the table(reloading data).
-(void) findEvents{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
[self fetchData];
});
// Add another task to the group
dispatch_group_async(group, queue, ^{
[self reloadTableData];
});
// Add a handler function for when the entire group completes
// It's possible that this will happen immediately if the other methods have already finished
dispatch_group_notify(group, queue, ^{
[self enjoyAfterwards];
});
}
-(void)fetchData{
[[ref child:#"calendar_event" ] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
for(FIRDataSnapshot* snap in snapshot.children){
[self.allEvents addObject:[[GAEvent alloc] initWithParams: snap]];
}
}];
}
-(void)reloadTableData{
NSLog(#"reloading table data");
}
-(void) enjoyAfterwards{
NSLog(#"enjoying");
}
This is my reference. But I am getting the following output:
reloading table data
enjoying
// data is fetched
In Swift, I can use callbacks to achieve this. How do I do this in Objective-C?
Related
In my iOS application I am using Core Data.
For table View listing I use NSFetchedResultsController and
Connecting to Remote store I use NSIncrementalStore.
My FetchedResultsController Context is having MainQueue Cuncurrency type.(I couldn't do it with a PrivateQueueCurrencyTYpe).
For resolving Fault, for a many relationship, the executeFetchResultsCall:withContext:error method is executed from my IncrementalStore subclass.
Inside the executeFetchResults method, I will invoke the API (connecting to remote server) if it is not available in my local database.
myarray = [object representationsForRelationship:#"manyconnection" withParams:nil];
Now I need the results array in return synchronously to be returned to the ExecuteFetchResultsMethod. Also this operation should be executed on Main thread.
So I am having only one option to fetch the results from server which causes the UI to unresponsive for the specified sleep time.
-(RequestResult*)makeSyncJsonRequest{
__block RequestResult *retResult = [[RequestResult alloc] init];
__block BOOL block = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
void (^resultBlock)(RequestResult*) = ^(RequestResult* result){
if(!retResult.error)
retResult = result;
block = NO;
dispatch_group_leave(group);
};
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
dispatch_group_enter(group);
[self makeAsyncJsonRequestWithBlock:resultBlock];
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return retResult;
}
As the above operation is being executed on main thread,the UI hangs.
Inorder to make the UI smoother, I need to carry out the executeFetchrequest in some other thread which is not possible.
It also expects the results array in return.
Is there any option to carry out this something like in a completion handler manner?
or
Any alternate methods or design to work this proper.
Any Help is highly appreciated.
This is a skeleton, using a dispatch_group, assuming you are using an NSFetchedResultsController to update your UITableView:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do your setup (FetchedResultsController and such)
[self syncData];
}
- (void)syncData
{
NSArray<Entity*> *results = [self fetchData];
BOOL needsUpdateFromServer = YES; // check your results and set this bool accordingly
if (!needsUpdateFromServer) {
return;
}
__block ServerResponse *fromServer = nil;
__block dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self loadDataFromServer:^(ServerResponse *response) {
fromServer = response;
dispatch_group_leave(group);
}];
dispatch_group_notify(group,dispatch_get_main_queue(),^{
[self persistData:fromServer];
/*
According to our discussion, you are using an NSFetchedResultsController.
So your tableView should update automatically after persisting the data.
*/
});
}
- (void)loadDataFromServer:(void (^)(ServerResponse *response))completion
{
// [someDownloadService downloadDataFromServerInBackgroundWithCompletion:^(ServerResponse* response){
dispatch_async(dispatch_get_main_queue(), ^{
completion(response);
});
// }];
}
- (NSArray<Entity*>*)fetchData
{
NSArray<Entity*> *results = nil;
// fetch from core data and return it
return results;
}
- (void)persistData:(NSArray<ServerResponse*> *)serverResponses
{
// parse whatever you get from server
// ... and persist it using Core Data
}
#end
I have a content that consists of main file and additional files.
So here is the problem: At first I need to download,unpack and insert into database additional files and only then do the same thing for main file. Additional files are needed to be downloaded serial, and main file must be downloaded after them.
What is the right way to do it?
Right now I'm doing it this way:
- (void)checkIfPlacesAreDownloaded:(NSArray *)places{
[SVProgressHUD showWithStatus:#"Downloading places"];
dispatch_group_t group = dispatch_group_create();
for(NSDictionary *place in places){
BOOL result = [IDGDatabaseManager checkIfPlaceIsDownloaded:place];
if(!result){
dispatch_group_enter(group);
[self downloadPlace:place withCompletionHandler:^{
[IDGDatabaseManager setPlaceDownloaded:[place objectForKey:#"place_ID"]
WithCompletionBlock:^(BOOL success, NSError *error) {
dispatch_group_leave(group);
}];
}];
}
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self downloadExcursionWithParams:self.excursionDownloadResponse];
});
}
It only works if there is one file in "places" array. If there is more than one file they start to be downloaded parallel and it is not suitable for me.
I think that downloadPlace:withCompletionHandler: method works asynchronously on the background concurrent queue. That is why the file downloads run in parallel. I'd use a private serial queue instead or simply do the next:
[SVProgressHUD showWithStatus:#"Downloading places"];
dispatch_group_t group = dispatch_group_create();
// create a serial background queue to run the file downloads
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
dispatch_queue_t myQueue = dispatch_queue_create("com.YourApp.YourQueue", qosAttribute);
for(NSDictionary *place in places){
BOOL result = [IDGDatabaseManager checkIfPlaceIsDownloaded:place];
if(!result){
dispatch_group_enter(group);
// run the download async on the serial bg queue
__weak __typeof(self) weakSelf = self;
dispatch_async(myQueue, ^{
__typeof(self) strongSelf = self;
// we need a semaphore to wait for the download completion
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[strongSelf downloadPlace:place withCompletionHandler:^{
[IDGDatabaseManager setPlaceDownloaded:[place objectForKey:#"place_ID"]
WithCompletionBlock:^(BOOL success, NSError *error) {
dispatch_semaphore_signal(sema);
}];
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_group_leave(group);
});
}
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self downloadExcursionWithParams:self.excursionDownloadResponse];
});
I have a for loop containing three asynchronous methods, and I want to make some treatment after this 3 async methods are finished.
-(void)getAllUsersInformations{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for(User *user in users){
[self getUserInfo:user];
}
//Here, I want to reload the table view for example, after finishing the for loop (executing the whole three methods).
});
}
-(void)getUserInfo:(User*)user{
[self getInformations:user];
[self getExperiences:user];
[self getEducation:user];
}
Do you have any technic to have this result?
Thank you very much.
One GCD approach is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):
If getInformations, getExperiences and getEducation are, themselves, all asynchronous methods, the first thing you need is some mechanism to know when they're done. A common solution is to implement a completion block pattern for each. For example:
// added completionHandler parameter which will be called when the retrieval
// of the "informations" is done.
- (void)getInformations:(User*)user completionHandler:(void (^)(void))completionHandler {
// do whatever you were before, but in the asynchronous task's completion block, call this
// completionHandler()
//
// for example
NSURLRequest *request;
[NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// handle the request here
// the important thing is that the completion handler should
// be called _inside_ the this block
if (completionHandler) {
completionHandler();
}
}];
}
Repeat this process for getExperiences and getEducation, too.
Then, you can use a dispatch group to notify you of when each of these three requests are done done, calling a completion block in getUserInfo when that takes place:
// added completion handler that will be called only when `getInformations`,
// `getExperiences` and `getEducation` are all done.
//
// this takes advantage of the completion block we added to those three
// methods above
- (void)getUserInfo:(User*)user completionHandler:(void (^)(void))completionHandler {
dispatch_group_t group = dispatch_group_create();
// start the three requests
dispatch_group_enter(group);
[self getInformations:user completionHandler:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self getExperiences:user completionHandler:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self getEducation:user completionHandler:^{
dispatch_group_leave(group);
}];
// this block will be called asynchronously only when the above three are done
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (completionHandler) {
completionHandler();
}
});
}
And you then repeat this process at the getAllUsersInformations:
// call new getUserInfo, using dispatch group to keep track of whether
// all the requests are done
-(void)getAllUsersInformations {
dispatch_group_t group = dispatch_group_create();
for(User *user in users){
dispatch_group_enter(group);
[self getUserInfo:user completionHandler:^{
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
Two final thoughts:
Having outlined all of that, I must confess that I would probably wrap these requests in concurrent/asynchronous custom NSOperation subclasses instead of using dispatch groups. See the "Configuring Operations for Concurrent Execution" section of the Concurrency Programming Guide. This is a more radical refactoring of the code, so I won't tackle that here, but it lets you constrain the number of these requests that will run concurrently, mitigating potential timeout issues.
I don't know how many of these user requests are going on, but you might want to consider updating the UI as user information comes in, rather than waiting for everything to finish. This is, again, a more radical refactoring of the code, but might lead to something that feels more responsive.
Try to do a block with completion, you can't do this with a for loop if the methods are async. you have to call getUserInfo one by one after the completion of the previous. I think this gonna be solved your problem.
-(void)getAllUsersInformations{
[self registerUserAtIndex:0];
}
- (void) registerUserAtIndex: (NSInteger ) userIndex
{
RegisterOperation *op = [[RegisterOperation alloc] initWithUser:[users objectAtIndex:userIndex]];
[RegisterOperation setResultCompletionBlock:^(BOOL *finished, NSInteger userIndex) {
dispatch_async(dispatch_get_main_queue(), ^{
if (userIndex++ < [users count] {
[self registerUserAtIndex:userIndex++];
} else {
[myTableView reloadData];
}
}];
[[NSOperationQueue mainQueue] addOperation:op];
}
Hope this will help you.
Rop Answer with swift:
func processData()
{
let group: dispatch_group_t = dispatch_group_create()
for item in data as! Object {
dispatch_group_enter(group)
item.process(completion: {() -> (Void) in
dispatch_group_leave(group)
})
}
dispatch_group_notify(group, dispatch_get_main_queue(), {
//Do whatever you want
})
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Background work
for(User *user in users){
[self getUserInfo:user];
}
dispatch_async(dispatch_get_main_queue(), ^{
//reload tableview , this is on main thread.
});
});
I have this in my code:
- (void)loadPanoramaForLocation:(CLLocation *)location
{
dispatch_group_t group = dispatch_group_create();
...
dispatch_group_enter(group);
[self getImageForPanorama:model level:level face:PLCubeFaceOrientationFront completion:^(PLImage * image) {
if (image) {
[cubicPanorama setTexture:[PLTexture textureWithImage:image] face: PLCubeFaceOrientationFront];
dispatch_group_leave(group);
}
}];
...
dispatch_group_enter(group);
[self getImageForPanorama:model level:level face:PLCubeFaceOrientationBack completion:^(PLImage * image) {
if (image) {
[cubicPanorama setTexture:[PLTexture textureWithImage:image] face: PLCubeFaceOrientationBack];
dispatch_group_leave(group);
}
}];
...
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
dispatch_release(group);
NSObject<PLIPanorama> *panorama = cubicPanorama;
[plView setPanorama:panorama];
});
}
I don't use ARC in this code and I want to know, is it the correct way to use dispatch_release here? Where should I put it? Is it ok to put it in dispatch_group_notify? Thanks
You can release it as the last thing in -loadPanoramaForLocation:. dispatch_group_notify() is documented to retain it until the notification block has run to completion. So, once you've called that, there's no need to keep your strong reference.
I have a method in which I run a couple of other methods. These have completion blocks, I only want to return a value at the end of my main method once I have a result from each of my sub methods. Example:
-(NSMutableDictionary *)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
}
[self subMethod2Complete:^(NSMutableArray *results)
{
}
//return...
}
I only want to return my dictionary at the end once the two sub method have completed. How can I do this?
I did have the idea of storing a BOOL for each method, so I know, NO incomplete and YES complete. So when both are YES, I return my dict. But how I can call it on time and not prematurely?
Update
I have tweaked my code to use a completion block, so when I finally receive the data from two other completion blocks from other methods, I run the final one with compiled results. Below you can see my method. You can see my method below, no success thus far, the final completion block is still getting called prematurely.
Important bits for me. getTitles and getThumbnails methods. In the completion block of these I get the data I need. Only when I have both of these, do I want to call my final completion block of this main method. As a result, it will pass on both titles and thumbnails once they have been received.
-(void)getFeedForUserID:(NSString *)channelID delegate:(id<YTHelperDelegate>)delegate complete:(void (^)(NSMutableDictionary * result))completionBlock properties:(NSString *)element, ...
{
va_list args;
va_start(args, element);
NSMutableArray *array = [NSMutableArray new];
for (NSString *arg = element; arg != nil; arg = va_arg(args, NSString *)) [array addObject:arg];
va_end(args);
NSMutableDictionary *resultsDict = [NSMutableDictionary new];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (NSString *string in array)
{
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[self getTitlesArrayForChannel:channelID completionHandler:^(NSMutableArray *results) {
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:results forKey:kFeedElementTitle];
});
}];
});
}
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[self getThumbnailsArrayForChannel:channelID completionHandler:^(NSMutableArray *results) {
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:results forKey:kFeedElementThumbnail];
});
}];
});
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completionBlock(resultsDict);
});
}
You can use GCD and the dispatch groups feature. Here's an article that explains it: http://www.objc.io/issue-2/low-level-concurrency-apis.html#groups
For example in your case, your code might look something like this (shamelessly copied from the article and adapted a bit)...
- (void)asyncMethod {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
NSMutableArray * results = [self subMethod1];
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
self.subMethod1Results = results;
});
});
dispatch_group_async(group, queue, ^(){
NSMutableArray * results = [self subMethod2];
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
self.subMethod2Results = results;
});
});
// This block will run once everything above is done:
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
// notify the app that both sets of data are ready
[self notifyWorkIsDone];
// and release the dispatch group
dispatch_release(group);
});
}
This requires a little modification to how your class works, because the above method is asynchronous (which is a good thing--it's not going to block your app while all that work is being done). All you need is some sort of handler to call and notify your app that your data is ready and you can update your UI or do whatever additional processing is necessary.
You are looking for GCD's dispatch_group APIs. Here is some sample code from Apple's Concurrency Programming Guide:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Comments on updated code:
Are you sure your mistake is not that your second if statement checks kFeedElementTitle a second time instead of kFeedElementThumbnail which I think may be what you intended?
Updated with working example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *kFeedElementTitle = #"some";
NSString *kFeedElementThumbnail = #"strings";
NSArray *array = #[#"some", #"test", #"strings"];
NSMutableDictionary *resultsDict = [NSMutableDictionary new];
NSLog(#"App launched");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (NSString *string in array)
{
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:5]; // simulate network call
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:#"title result" forKey:kFeedElementTitle];
NSLog(#"Received title result");
});
});
}
if ([string isEqualToString:kFeedElementThumbnail]) // Note: this was changed to kFeedElementThumbnail from kFeedElementTitle
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:10]; // simulate network call
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:#"thumbnail result" forKey:kFeedElementThumbnail];
NSLog(#"Received thumbnail result");
});
});
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"final dictionary: %#", resultsDict);
});
return YES;
}
Output:
2013-07-16 21:02:46.468 d[947:a0b] App launched
2013-07-16 21:02:51.471 d[947:a0b] Received title result
2013-07-16 21:02:56.471 d[947:a0b] Received thumbnail result
2013-07-16 21:02:56.472 d[947:a0b] final dictionary: {
some = "title result";
strings = "thumbnail result";
}
you do not know when the blocks are going to return so you will not know if you have the data at the time, if i may make a suggestion you call a method with in those blocks that method will check to see if both dictionaries are set and if they are then continue with the process otherwise don't continue
- (void)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
self.result1 = results;
[self method3];
}
[self subMethod2Complete:^(NSMutableArray *results)
{
self.results2 = results;
[self method3];
}
}
- (void)method3 {
if ( self.results1 != nil && self.results2 != nil ) {
[self startProcedure];
} else {
// do nothing
}
}
although all together i would suggest reworking your code to do this differently, simply because you can't guarantee that one of the blocks will be done by the time of the return, let alone both of them
you can also do something like this
-(NSMutableDictionary *)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
}
[self subMethod2Complete:^(NSMutableArray *results)
{
}
while(result == nil)
sleep(1);
//return...
}
which again is really bad.... it's just better to re-write the code