16 Replies
      Latest reply on Mar 8, 2020 10:41 AM by PBK
      ace.neerav Level 1 Level 1 (0 points)

        my call makes a call to HKSampleQuery method initWithSampleType that queries information from healthkit store. I want the main thread to wait until the the query is over so that the block handler updates the global variables in the custom class.

         

        I understand concurrency but haven't used it enough. Please help me with this one.

         

        my code goes like this...

         

        [self methodone];
        do something based on result
        
        -(void)methodone
        {
        api call with blockhandle
        { set result to global variables in block};
        
        }
        • Re: continue in main thread after block has executed.
          PBK Level 7 Level 7 (3,765 points)

          > I want the main thread to wait until the the query is over so that the block handler updates the global variables in the custom class.

           

          Not sure what you are asking.  But do you realize that the code you place within the block handler will execute after the results have come in?  If you place within that block handler something like 

                [self runThisMethod:variables]

          then 'runThisMethod' and, everything that it calls, will be run after your query returns with the variables.

           

            • Re: continue in main thread after block has executed.
              ace.neerav Level 1 Level 1 (0 points)

              I know, all code inside the handler will be executed if there are results. Maybe there is a better way to do this.

               

              I want to get the weight and the steps of the user from HealthKit and perform some calculations on it. Assuming there are records (like on my iPhone), the weight and steps variables are set later and the calculations happen before resulting in a 0.

               

              I think dispatch_sync is the answer but I dont know how to use it. Like the queue to use. I know i want to dispatch the block synchronously.

                • Re: continue in main thread after block has executed.
                  PBK Level 7 Level 7 (3,765 points)

                  Again, your problem is unclear.

                   

                  > Assuming there are records (like on my iPhone), the weight and steps variables are set later and the calculations happen before resulting in a 0.

                   

                  Your block will execute when the data that the method went out to obtain is obtained. Why then is something "set later".  Whatever the reason for that, after it is set update your app at that time.

                   

                  You may need this code in your block handler:

                  dispatch_async(dispatch_get_main_queue(), ^{
                  
                                //   insert code here
                  
                  
                     });
                  
                    • Re: continue in main thread after block has executed.
                      ace.neerav Level 1 Level 1 (0 points)

                      The block is where the data is fetched from the healthkit. Its a healthkit api call. The block sets the global variables (which I do using self->variable). The. The method to do the calculations is called on the main thread. So I want to hold the main thread u till then. If I use dispatch async, it won't happen. If I use dispatch sync and pass main thread it will enter deadlock.

                        • Re: continue in main thread after block has executed.
                          PBK Level 7 Level 7 (3,765 points)

                          I don't really understand the nuances of this issue (i.e. 'deadlock' and 'pass main thread') but I wonder....

                           

                          " The method to do the calculations is called on the main thread"

                           

                          Why can't you do that within the block? That is:

                           

                          Insert this code in your block handler:

                          dispatch_async(dispatch_get_main_queue(), ^{
                               //set your global variables
                               //  call the method to do the calculations
                               //      (or set some BOOL global variable - see below)
                           }); 
                          
                          
                          

                          If you are waiting for something else to happen before calling the method to do the calculation then set a BOOL global variable in there and don't call that method if the BOOL is not set.  Start a timer and keep checking to see when the BOOL gets set

                            • Re: continue in main thread after block has executed.
                              ace.neerav Level 1 Level 1 (0 points)

                              I think what I need to know is how to dispatch synchronously on a concurrent queue?

                               

                              When the concurrent queue has finished executing tasks (and i want it to be serially), it returns to the main thread and then the main thread continues execution.

                               

                              If i put the part of code that does the calculations in a method called from the block, the part of code beyond querying the records is till executed in the meanwhile, which is not desireable.

                                • Re: continue in main thread after block has executed.
                                  PBK Level 7 Level 7 (3,765 points)

                                  I am trying to understand what you mean by:

                                   

                                  "If i put the part of code that does the calculations in a method called from the block, the part of code beyond querying the records is till executed in the meanwhile"

                                   

                                  "beyond" "till"  "in the meanwhile"  - I think they all refer in an ambiguous way to a sequence in time.  But I have no idea if that is correct and if so, which comes first - beyond, still or meanwhile.

                                   

                                  But in any case, the completion block will execute after the query is done.  It may execute on a thread that does not release itself.  If so, that thread will never allow any changes you make in your display on that thread to actually appear on the screen because the thread is blocked - the thread is running continually and never ends.  Therefore, you want to place the code in that 'dispatch_async' enclosure so that the code is run on a thread that ends and allows the system to refresh the display.

                                    • Re: continue in main thread after block has executed.
                                      ace.neerav Level 1 Level 1 (0 points)

                                      If I dispatch asynchronously, the code after that dispatch block still gets executed before the block finishes execution. My app cannot proceed without the Information. So I need to dispatch synchronously on a concurrent thread. Can u tell me the code for it. Is the global queue for it?

                                      • Re: continue in main thread after block has executed.
                                        ace.neerav Level 1 Level 1 (0 points)

                                        here is my class with the dispatch statement i wrote and weight, steps and cal burned are zero...

                                         

                                        //
                                        //  Calories.m
                                        // 
                                        //
                                        //  Created by Neerav Kothari on 28/02/20.
                                        //  Copyright © 2020 Neerav Kothari. All rights reserved.
                                        //
                                        
                                        #import "Calories.h"
                                        #import 
                                        #import "HealthKitOperator.h"
                                        #import "AppDelegate.h"
                                        
                                        @interface Calories ()
                                        {
                                            float weight;
                                            AppDelegate *appDelegate;
                                            int steps;
                                        }
                                        
                                        @end
                                        
                                        @implementation Calories
                                        
                                        -(float)calculateCaloriesBurnedForWalkingType: (int)type
                                        {
                                            dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
                                            dispatch_sync(queue, ^(){
                                                [self queryWeight];
                                                [self querySteps];
                                            });
                                            
                                           // don't want to execute beyond this point (in this method) until weight and steps are set.
                                        
                                        
                                            #ifdef DEBUG
                                            NSLog(@"weight: %f",weight);
                                            #endif
                                            
                                            #ifdef DEBUG
                                            NSLog(@"steps: %f",steps);
                                            #endif
                                        
                                        //formula follows...
                                            
                                            return caloriesBurnedInStepsTaken;
                                        }
                                        
                                        -(float)queryWeight
                                        {
                                            
                                            [self queryForQuantityTypeIdentifier:HKQuantityTypeIdentifierBodyMass];
                                            return weight;
                                        }
                                        
                                        -(int)querySteps
                                        {
                                            [self queryForQuantityTypeIdentifier:HKQuantityTypeIdentifierStepCount];
                                            return steps;
                                        }
                                        
                                        -(void)queryForQuantityTypeIdentifier: (HKQuantityTypeIdentifier)quantityTypeIdentifier;
                                        {
                                            HKSampleType *weightSampleType = [HKSampleType quantityTypeForIdentifier:quantityTypeIdentifier];
                                            HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightSampleType predicate:nil limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
                                                if (!results) {
                                                    NSLog(@"Error is fetching weight %@", error);
                                                }
                                                else
                                                {
                                                    HKQuantitySample *quantitySample =(HKQuantitySample *)results[0];
                                                    HKQuantity *quantity = [quantitySample quantity];
                                                    #ifdef DEBUG
                                                    NSLog(@"Quantity: %@",quantity);
                                                    #endif
                                                    if (quantityTypeIdentifier == HKQuantityTypeIdentifierBodyMass)
                                                    {
                                                        self->weight = ([quantity doubleValueForUnit:[HKUnit gramUnit]])/1000;
                                                    }
                                                    else if (quantityTypeIdentifier == HKQuantityTypeIdentifierStepCount)
                                                    {
                                                        self->steps = [quantity doubleValueForUnit:[HKUnit countUnit]];
                                                    }
                                                }
                                                }];
                                        
                                            appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
                                            HKHealthStore *store = [[appDelegate hkOperator] hkStore];
                                            [store executeQuery:query];
                                        }
                                        
                                        
                                        @end
                                          • Re: continue in main thread after block has executed.
                                            janabanana Level 1 Level 1 (10 points)

                                            Why do you have float weight and int steps declared in the interface? 

                                            and why is steps an int when it is a double in queryForQuantityTypeIdentifier?

                                             

                                            Declare those variables in -(float)calculateCaloriesBurnedForWalkingType: (int)type

                                             

                                            In your queryForQuantityTypeIdentifier, I would return a double.

                                             

                                            Return that double from queryWeight and querySteps.

                                             

                                            Then send the steps and weight to:  caloriesBurnedInStepsTaken:(double)steps weight:(double)weight and have it return the result.

                                             

                                            You can then probably do away with the dispatch stuff.

                                             

                                            Not tested, but this is what I would do instead of having those variables like they are. 

                                            • Re: continue in main thread after block has executed.
                                              PBK Level 7 Level 7 (3,765 points)

                                              !!!

                                              "If I dispatch asynchronously, the code after that dispatch block still gets executed before the block finishes execution."

                                               

                                              Yes - but the code in a completion block only gets executed when the completion block gets executed - and that happens after you get results.

                                               

                                              Put the code in the completion block.  That's this method!!!

                                                HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightSampleType predicate:nil limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {  
                                                      if (!results) {  
                                                          NSLog(@"Error is fetching weight %@", error);  
                                                      } 
                                                      else  
                                                      {  
                                              
                                              
                                              
                                              
                                              
                                              
                                                                         //PUT THE CODE HERE     
                                              
                                              
                                              
                                              
                                              
                                              
                                              
                                                          HKQuantitySample *quantitySample =(HKQuantitySample *)results[0];  
                                                          HKQuantity *quantity = [quantitySample quantity];  
                                                          #ifdef DEBUG  
                                                          NSLog(@"Quantity: %@",quantity);  
                                                          #endif  
                                                          if (quantityTypeIdentifier == HKQuantityTypeIdentifierBodyMass)  
                                                          {  
                                                              self->weight = ([quantity doubleValueForUnit:[HKUnit gramUnit]])/1000;  
                                                          }  
                                                          else if (quantityTypeIdentifier == HKQuantityTypeIdentifierStepCount)  
                                                          {  
                                                              self->steps = [quantity doubleValueForUnit:[HKUnit countUnit]];  
                                                          }  
                                                      }  
                                                      }]; 
                                              
                                              

                                               

                                               

                                               

                                                • Re: continue in main thread after block has executed.
                                                  ace.neerav Level 1 Level 1 (0 points)

                                                  I understand what you are saying. i cannot do the calculations there as the block is called twice. although i can set a class level flag which sees to it that the calculations happen only after both variables have a value set. i'll try that.

                                                   

                                                  i the meanwhile, if i declare the block before and pass it as an arguments, can i mark it to be dispatched syhcnronolsuly only (while the system automatically dispatches it on a concurrent queue)?

                                                  A lot of system frameworks have api calls with blocks. so knowing this would be really helpful.

                                                  • Re: continue in main thread after block has executed.
                                                    ace.neerav Level 1 Level 1 (0 points)

                                                    I am still stuck after 2 days. Thanks for being around. We will solve this.

                                                     

                                                    My main problem is the block will return immediatelly and doing calculation even inside the block would make the main thread continue.

                                                      • Re: continue in main thread after block has executed.
                                                        PBK Level 7 Level 7 (3,765 points)

                                                        I believe the method initWithSampleType creates a query which you have called "query" (IMHO better programming - call such things myQuery)  That query called "query" has a completion block.  That completion block will only be run when the query returns a result.  The query will return a result whenever there is a result to return but first you must get "query" running.  You got "query" running in your code when you got to "[store executeQuery:query];"  Once the query is executing it could return results multiple times - recall 'it is running on a background thread'.  (It will not stop running until you call 'stopQuery' on it.)  Each time it gets a result it will execute that completion block.  But it will execute that completion block on that background thread - but don't worry - in that completion block you have that "dispatch_async(dispatch_get_main_queue()" which gets your code back on the main thread. 

                                                          • Re: continue in main thread after block has executed.
                                                            ace.neerav Level 1 Level 1 (0 points)

                                                            no matter what i do, the man flow returns at the earlies point i try to reference cal burned class.

                                                             

                                                            i'll describe u the issue. if u can tell me how to do it, i'll be very thankfully. have seens replies from you on many posts previously.

                                                             

                                                            what i want to do...

                                                            • query weight and steps from healthkitstore.
                                                            • calculate calories burned as a result.
                                                              • Re: continue in main thread after block has executed.
                                                                PBK Level 7 Level 7 (3,765 points)

                                                                Let's clear up an issue.  In your code will you please add the NSLog line in this:

                                                                 

                                                                
                                                                    appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];  
                                                                    HKHealthStore *store = [[appDelegate hkOperator] hkStore];   
                                                                    NSLog(@"going to execute the query NOW");
                                                                    [store executeQuery:query];  
                                                                
                                                                

                                                                 

                                                                Then run and tell us whether it printed "going to execute the query NOW" before it printed your "Quantity: " and how often it printed your "Quantity: " and what values it gave for your quantity?