0

I have a app a app that allows users to rapidly send photos to each other , but as we all know users dont always have a perfect internet connection so we decided to create a system that would temp store all the photos in a directory and info for each api request in a array of dictionaries. If a user takes 2 photos to send and the first one fails due to no connection and then a few minutes later the user takes a 3rd photo when they have a internet connection this is what happens (pseudo ) , but we get some repeats and weird things happening doing it this way if the queue starts to get backed up and this whole process is being triggered multiple times. So we did some research and dispatch_groups seems to be the answer, but we cant figure out how we can use the same dispatch group each time so that there are not multiple dispatch group queues all firing the same requests at the same time if a user takes 20 pictures really fast.

Another important part of this system is that it must MUST upload in the same order all the images were taken and should ideally avoid any duplication

-(void)upload:(NSString*)typeOfUpload{  

    [_resendBtn setHidden:YES];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSMutableArray *pendingRequests=[[NSMutableArray alloc] init];

    NSString *pendingRequestsFrom= [NSString stringWithFormat:@"pendingRequestsForUid%@",[defaults objectForKey:@"uid"]];
    NSLog(@"PENDINGREQUESTFROM:%@",pendingRequestsFrom);


    if ([defaults objectForKey:pendingRequestsFrom]){
        pendingRequests= [[defaults objectForKey:pendingRequestsFrom]mutableCopy];
    }





    NSMutableDictionary *requestDict=[[NSMutableDictionary alloc] init];
    NSDate *now = [NSDate date];
    int timestamp = [[NSDate date] timeIntervalSince1970];
    [requestDict setObject:[NSString stringWithFormat:@"%d",timestamp] forKey:@"timestamp"];

    if(_convertedVideoURL){
        NSString*urlPath= [_convertedVideoURL path];
        [requestDict setObject:urlPath forKey:@"videoURL"];
    }

    if([typeOfUpload isEqualToString:@"PHOTO"]){

        // Get image data. Here you can use UIImagePNGRepresentation if you need transparency
        NSData *imageData = UIImageJPEGRepresentation(_imgToSend, 8);

        // Get image path in user's folder and store file with name image_CurrentTimestamp.jpg (see documentsPathForFileName below)

         //Create temporary URL to record to
         NSDate *now = [NSDate date];
         NSTimeInterval nowEpochSeconds = [now timeIntervalSince1970];
         NSString *intervalString = [NSString stringWithFormat:@"%f", nowEpochSeconds];

         NSString *main_img_path = [[NSString alloc] initWithFormat:@"%@image%@.jpg", NSTemporaryDirectory(), intervalString];

        // Write image data to user's folder
        [imageData writeToFile:main_img_path atomically:YES];



        [requestDict setObject:main_img_path forKey:@"imgToSendStored"];
    }
    [requestDict setObject:_selectedUserString forKey:@"recip_uid"];
    [requestDict setObject:typeOfUpload forKey:@"MEDIA_TYPE"];
    if([typeOfUpload isEqualToString:@"TEXT"]){
        [requestDict setObject:_textMsgView.coverCaption.text forKey:@"body"];
    }
    NSLog(@"params being stored for later %@", requestDict);
    [pendingRequests addObject:requestDict];



        NSArray *newArray= [NSArray arrayWithArray:pendingRequests];

    NSLog(@"TOTAL_PENDING_VIDS == %@, araay count == %d",newArray,[newArray count]);


    [defaults setObject:newArray forKey:pendingRequestsFrom];
    [defaults synchronize];
    _imgToSend=nil;
    _textToSend=nil;
    _isTextDropDownDisplayed=NO;
    [UIView animateWithDuration:.5 animations:^{

        [_textMsgView setFrame:CGRectMake(0, -300, 320, 10)];
        _textMsgView.coverCaption.text=@"";
        //secondView.alpha = 1.0;
        [self swippedAway];

    }];
    [self uploadStoredVidsFunction:@"UPLOAD"];
}



-(void)uploadStoredVidsFunction:(NSString*)typeOfResend
{



    NSString *pendingRequestsFrom= [NSString stringWithFormat:@"pendingRequestsForUid%@",[defaults objectForKey:@"uid"]];
    pendingRequests= [[defaults objectForKey:pendingRequestsFrom]mutableCopy];
    NSLog(@"PENDING_REQUESTS%@",pendingRequests);
    dispatch_group_t group = dispatch_group_create();
    for (int i=0;i<[pendingRequests count]; i++) {



         dispatch_group_enter(group);



        MAKE AFNETWORKING  REQUEST  

        success{
             remove request from pending array
           // start next request 
            dispatch_group_leave(group);

       } 
        failure {
            //STOP THE QUEUE from continuing to execute the rest of the requests in line/give user their options  ( aka  retry sending all/ delete all/save for later ) 
        }



}



}
ChuckKelly
  • 1,742
  • 5
  • 25
  • 54

1 Answers1

2

You could just spawn off a new thread that deals with all of this in an unterminating while loop using an NSCondition to ensure thread-safety.

// Somewhere in your initialization:
requestLock = [[NSCondition alloc] init];
[self performSelectorInBackground:@selector(processRequests)];

- (void)processRequests {
  while (![[NSThread currentThread] isCancelled]) {
    [requestLock lock];
    if ([pendingRequests count] == 0 /* || delay time not yet reached */) {
      [requestLock waitUntilDate:someTimeoutDate];
      [requestLock unlock];
      continue;
    }
    NSMutableArray *remainingRequests = [pendingRequests copy];
    [pendingRequests removeAllObjects];
    [requestLock unlock];
    for (Request *request in requests) {
      if (success) {
        // Process the request and then..
        [remainingRequests removeObject:request];
      } else {
        break;
      }
    }
    [requestLock lock];
    [pendingRequests insertObjects:remainingRequests atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [remainingRequests count])]];
    [requestLock unlock];
  }
}

- (void)addRequest:(Request *)request {
  [requestLock lock];
  [pendingRequests addObject:request];
  [requestLock broadcast];
  [requestLock unlock];
}
Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • For starters thank you for your responses , but on our system a user can add pending photos as fast as they can tap so wouldnt the first solution just have several queues of the same requests going and/or be at risk of the requests being out of order for instance say i have 5 pictures in pendingRequests, i clear all those out to attempt to send them meanwhile 5 more are taken and a new thread is created , thread one will try to add the requests back into pending requests at the indexes of 0 to 5 first and then the second set will try to insert at indexes 0 to 5 ending w. 6,7,8,9,10,1,2,3,4,5? – ChuckKelly Oct 16 '14 at 20:04
  • As far as the second part goes im not quite clear on what request lock is? – ChuckKelly Oct 16 '14 at 20:05
  • The first solution makes some assumptions about how frequently you're calling the `for` loop. If you call it every time you get a new item in the queue regardless of the current queue processing state, then yes, you will have issue. You could step around that with a `BOOL` flag, I guess. ;;; The `requestLock` is an instance variable `NSCondition *` object. It doesn't do anything other than ensure you're modifying `pendingRequests` while still respecting thread safety. – Ian MacDonald Oct 16 '14 at 20:10
  • I updated my answer to remove the initial `dispatch_async` method; in order to be sure that you're processing all of the requests in order, you'll need to put them into a single queue. – Ian MacDonald Oct 16 '14 at 20:14
  • in this revised answer are you making the assumption that all of this is in its own thread off of the main thread as well bc if not wouldnt this " while (![[NSThread currentThread] isCancelled]) {"" freeze the main ui thread? – ChuckKelly Oct 16 '14 at 20:21
  • The `performSelectorInBackground:` spawns the new thread. With the current state of the example source above, you should only ever call this once. – Ian MacDonald Oct 16 '14 at 20:23
  • Alright sorry for all the questions but , just to make sure i have this solid....that line u mentioned above is called once upon initialization of the class and then from then on every time a user taps to take/send a pic we just call addRequest and it will be processed inside of processRequests w. out us ever having to directly call processRequests again bc of how everything is setup? – ChuckKelly Oct 16 '14 at 20:29
  • p.s upvoted a few of ur other q's for all the trouble :p – ChuckKelly Oct 16 '14 at 20:29
  • That's correct. Your process thread will be waiting for a notification from `addRequest:` (the `broadcast` line) or until the `someTimeoutDate` (which you should reset before each `waitUntilDate:` call to keep it current. When the thread is processing, more elements can be added to `pendingRequests` and will be processed after the currently executing loop. If a request fails, it gets put back at the beginning of the `pendingRequests` array. – Ian MacDonald Oct 16 '14 at 20:35
  • Is there a reason 'waitUntilDate' is needed if the trigger is broadcast from another function? Also if there is a timeout on a thread would calling 'wait accomplish the same goal? Thanks – brettwmc Oct 16 '14 at 21:20
  • Yeah im unable to get this to work , idk if its due to my own inability to understand NSConditions on a deep level or what but for some reason it never gets beyond the initial call to it/never uploads anything even after copying this line for line . idk maybe this is just a bad approach all together and i need to rethink how to do this. – ChuckKelly Oct 16 '14 at 22:31
  • There's no theory reason to use `waitUntilDate:` instead of `wait`, but in my experience, I don't like leaving threads waiting indefinitely .. just in case. – Ian MacDonald Oct 17 '14 at 13:57
  • @ChuckKelly: Which "initial call to it" are you describing here? Does your application hang? If you hit 'pause' in the debugger, do you see an upload thread (put `[[NSThread currentThread] setName:@"Upload Thread"]` at the top of `processRequests`)? I'd prefer to help you get to a solution (especially since you've accepted this answer). – Ian MacDonald Oct 17 '14 at 14:00