{"id":1028,"date":"2013-11-16T19:42:06","date_gmt":"2013-11-16T19:42:06","guid":{"rendered":"https:\/\/zogspat.tk\/blog\/?p=1028"},"modified":"2013-11-16T20:08:30","modified_gmt":"2013-11-16T20:08:30","slug":"cmmotionactivitymanager-queues-and-async-behaviour","status":"publish","type":"post","link":"https:\/\/the-plot.com\/blog\/?p=1028","title":{"rendered":"CMMotionActivityManager, NSOperationQueues and Async Behaviour"},"content":{"rendered":"<p>I&#8217;ve just deleted a couple of articles from the blog for the first time in five years because, having spent quite a few hours going over the example code I&#8217;d posted, I realised that it was rubbish.<\/p>\n<p>There is a characteristic of some of the CMMotionActivityManager methods which gets you into some serious threading detail: they return results to an NSOperationsQueue asynchronously. What I was doing with the simpler of the two, queryStepCountStartingFrom, was to wrap a looping call to the inner results queue in an outer queue. So: I have an &#8216;inner&#8217; results queue, which is being called as a parameter to the queryStepCountStartingFrom method, effectively adding results which return asynchronously. I then force this to be a prerequisite by making the entire loop the first add operation on an outer queue. When the loop is done I make the call to the Core Plot charting methods, because all of the async data will have returned before the charting operation is added. Right?<\/p>\n<p>Wrong.<\/p>\n<p>It&#8217;s pure chance that the async operations return in time. The queue will merrily accept new operations regardless of whether the previous tasks have completed. <\/p>\n<p>These problems emerged when I tried to do the same thing with the slightly more complex queryActivityStartingFromDate method, which returns a richer data set, and so the async calls take significantly longer to return. I&#8217;ve been round the houses on this: the nested queues approach didn&#8217;t work, nor did a GCD async dispatch queue wrapped around the loop. I also tried messing around with adding dependencies between nested operations. No joy. <\/p>\n<p>The fundamental problem [so far as I can see] is that the first port of call for identifying the end of some queue activity, waitUntilAllOperationsAreFinished, is a synchronous animal and therefore has no purview on the results coming back asynchronously on the queue.<\/p>\n<p>What I really want to avoid is what I&#8217;ve done elsewhere when I&#8217;ve understood the problem less clearly, which is to mess around with timers. This is a pretty fragile approach.<\/p>\n<p>Googling for this has led me to the conclusion that the &#8216;right&#8217; way &#8211; i.e., to reliably detect the async results coming back &#8211; is to do something pretty low level with either threading or GCD.<\/p>\n<p>As a compromise, what I&#8217;ve come up with is to set an observer on the queue, which I first serialise [maxConcurrentOperationCount = 1]. In the observeValueForKeyPath method, I can test for the queue being empty, and increment a counter. When the counter hits the threshold for the number of day&#8217;s worth of data \/ loops round the CMMotionActivityManager methods for [i.e., 7], I am done. It ain&#8217;t pretty but it works.<br \/>\nToday. <\/p>\n<p>FWIW, here&#8217;s the code:<\/p>\n<pre>\r\n- (void) getDailyActivityTotals\r\n{\r\n    NSLog(@\"Entering getDailyTotals\");\r\n    \/\/ activityQueue is declared as a property as we need to refer to it in the observer method:\r\n    activityQueue.maxConcurrentOperationCount = 1;\r\n    activityQueue.name =@ \"activityQueue\";\r\n    [activityQueue addObserver:self forKeyPath:@\"operations\" options:0 context:NULL];\r\n    \r\n    NSLog(@\"starting to loop in calActivityOpn\");\r\n    for (int dayOffset = 6; dayOffset >=0; dayOffset--)\r\n    {\r\n        NSMutableArray *dailyResultsTmpArray = [[NSMutableArray alloc] init];\r\n        \/\/ startAndEndDates is a method which uses NSCalendar and NSDateComponents to\r\n        \/\/ create some NSDate objects based on an offset: 24 hour periods - start and end dates:\r\n        dailyResultsTmpArray = [self startAndEndDates:dayOffset];\r\n        [motionActivity queryActivityStartingFromDate:dailyResultsTmpArray[1]\r\n                                               toDate:dailyResultsTmpArray[0]\r\n                                              toQueue:activityQueue\r\n                                          withHandler:^(NSArray *activities, NSError *error)\r\n        {\r\n            NSInteger walkingCount = 0;\r\n            NSInteger runningCount = 0;\r\n            NSInteger carCount = 0;\r\n            NSInteger staticCount = 0;\r\n            NSInteger actionItemCount = 0;\r\n            \/\/ This returns a lot of data, so we are just counting the number of events\r\n            \/\/ per activity category:\r\n            for (CMMotionActivity *actItem in activities)\r\n            {\r\n                if (actItem.walking == 1)\r\n                    walkingCount++;\r\n                if (actItem.running == 1)\r\n                    runningCount++;\r\n                if (actItem.automotive == 1)\r\n                    carCount++;\r\n                if (actItem.stationary == 1)\r\n                    staticCount++;\r\n                actionItemCount++;\r\n            }\r\n            \/\/NSLog(@\"walking: %i\", walkingCount);\r\n            DailyActivityObject *todaysResults = [[DailyActivityObject alloc] initWithNumWalkCount:walkingCount numRun:runningCount nummAuto:carCount numStatic:staticCount numAction:actionItemCount forDay:dailyResultsTmpArray[0]];\r\n            [dailyResults addObject:todaysResults];\r\n            NSLog(@\"adding dailyResults\");\r\n                \r\n        }];\r\n\r\n    }\r\n}\r\n\r\n<\/pre>\n<p>and the KVO method:<\/p>\n<pre>\r\n- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object\r\n                         change:(NSDictionary *)change context:(void *)context\r\n{\r\n    if (object == activityQueue && [keyPath isEqualToString:@\"operations\"]) \r\n    {\r\n        \/\/ if the queue is empty - remember we have serialised:\r\n        if (activityQueue.operationCount == 0)\r\n        {\r\n            oberserverCount ++;\r\n            NSLog(@\"queue has completed %i iterations\", oberserverCount);\r\n            if (oberserverCount == 7)\r\n            {\r\n                \/\/ we have all the charting data so....\r\n                [activityQueue addOperationWithBlock:^{\r\n                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{\r\n                        [self plotCompoundChart:dailyResults];\r\n                    }];\r\n                }];\r\n            }\r\n        }\r\n    }\r\n    else {\r\n        [super observeValueForKeyPath:keyPath ofObject:object\r\n                               change:change context:context];\r\n    }\r\n}\r\n\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve just deleted a couple of articles from the blog for the first time in five years because, having spent quite a few hours going over the example code I&#8217;d posted, I realised that it was rubbish. There is a &hellip; <a href=\"https:\/\/the-plot.com\/blog\/?p=1028\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1028","post","type-post","status-publish","format-standard","hentry","category-ios-development"],"_links":{"self":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1028","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1028"}],"version-history":[{"count":5,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1028\/revisions"}],"predecessor-version":[{"id":1031,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1028\/revisions\/1031"}],"wp:attachment":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1028"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1028"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1028"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}