I'm new using dispatch_queue_t and I have two problems with my app which I think that could be solved using GCD. The first one is that in the main view (ViewController) I have a label which is actualized by another class (AudioViewController), and when I do any user interaction in ViewController the label stop to actualize, so I think if I use dispatch_queue_t this problem will be solved.
The second thing is that in the same main view (ViewController) when I press a contribution I call another class (ContributionViewController) and this class accesses just a instance variable of another class (AudioViewController) which is refreshed all the time. When I start contribution, I made a loop to get more than one value to make some calculus with them and those values are all the same though.
I'll put some code here trying to clear the things.
ViewController.m
- (IBAction)makeContribution:(id)sender
{
NSLog(#"A: Contribution button clicked");
NSLog(#"-= START CONTRIBUTION =-");
cvc = [[ContributionViewController alloc] init];
cvc.avc = self.avc;
// Get NUM_CONTRIBUTIONS contributions to make average.
int contContribution;
for (contContribution = 0; contContribution < NUM_CONTRIBUTIONS; contContribution++) {
[cvc getEachContribution];
}
// Make average
[cvc makeAverage:NUM_CONTRIBUTIONS];
[cvc release];
}
AudioViewController.m
- (void)audioInitializationWithTimeInterval:(float)time
{
NSDictionary* recorderSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:44100],AVSampleRateKey,
[NSNumber numberWithInt:1],AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
nil];
NSError* error;
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recorderSettings error:&error];
//enable measuring
//tell the recorder to start recording:
[recorder record];
if (recorder) {
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
[recorder record];
levelTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(levelTimerCallback:) userInfo:nil repeats:YES];
}
else
NSLog(#"%#",[error description]);
}
- (void)levelTimerCallback:(NSTimer *)timer
{
//NSLog(#"-= AVC =-");
[recorder updateMeters];
db = [recorder averagePowerForChannel:0] - DBOFFSET;
db += 120;
db = db < 0 ? 0 : db;
vc.lab_decibel.text = [NSString stringWithFormat:#"%0.0f", db];
}
ContributionViewController.m
- (void)getEachContribution
{
actualContribution = self.avc.db;
NSLog(#"Actual contribution: %f", actualContribution);
NSLog(#"Sum before: %0.2f", sumContribution);
sumContribution += actualContribution;
NSLog(#"Sum After: %0.2f", sumContribution);
}
- (void)makeAverage:(int)numOfContributions
{
self.average = self.sumContribution / numOfContributions;
NSLog(#"Average: %0.2f", self.average);
}
So, the main thing is dispatch_queue_t is going to solve my problems and how to do that? I've tried to put dispatch_queue_t on AudioViewController, ContributionViewController and ViewController, but the first didn't update the label, the second crashed and the third one the label still with 0 value.
Thanks for any tips to solve this problem.
EDIT 01:
The decibel label changes all the time.
Ok, this is starting to make sense. I didn't understand the mapView, contribution, etc. language. It was entirely cryptic, but I think I'm starting to understand your question. As I understand it, your question is why your user interface is not being updated. Ok, the big answer to your question is that you don't need GCD if you don't want to. The NSTimer does what you need.
Now, you say that your continuously updated db field is no longer updated. There's absolutely no reason why it shouldn't continue. I've tried it, and it works fine. There's something going on there. Personally, when I've got a NSTimer that is updating my UI, I make sure to turn it off in viewDidDisappear (no point in updating your UI if it's not visible) and always turn it on in viewDidAppear. When I did this, whenever I returned to my main view with the db numbers, it happily resumed. Or if I wanted another view controller to get the notifications, I just turned it back on using a delegate.
That might look like the following.
First, you probably want a delegate protocol:
// DecibelNotifierDelegate.h
#import <Foundation/Foundation.h>
#protocol DecibelNotifierDelegate <NSObject>
#required
- (void)decibelNotifierUpdate:(CGFloat)db;
#end
And then, you want to define your DecibelNotifier class:
// DecibelNotifier.h
#import <Foundation/Foundation.h>
#import "DecibelNotifierDelegate.h"
#interface DecibelNotifier : NSObject
#property (nonatomic, retain) id<DecibelNotifierDelegate> delegate;
#property CGFloat db;
- (void)startWithInterval:(float)time target:(id<DecibelNotifierDelegate>)delegate;
- (void)stop;
#end
and
// DecibelNotifier.m
#import "DecibelNotifier.h"
#import "AVFoundation/AVFoundation.h"
#interface DecibelNotifier ()
{
AVAudioRecorder *_recorder;
NSTimer *_levelTimer;
}
#end
// note, your code makes reference to DBOFFSET and I don't know what that is. Update this following line accordingly.
#define DBOFFSET 0
#implementation DecibelNotifier
#synthesize delegate = _delegate;
#synthesize db = _db;
- (id)init
{
self = [super init];
if (self)
{
NSDictionary* recorderSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:44100],AVSampleRateKey,
[NSNumber numberWithInt:1],AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
nil];
NSError* error;
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recorderSettings error:&error];
if (!_recorder)
NSLog(#"%#",[error description]);
}
return self;
}
- (void)startWithInterval:(float)time target:(id<DecibelNotifierDelegate>)delegate
{
if (_recorder)
{
self.delegate = delegate;
[_recorder prepareToRecord];
_recorder.meteringEnabled = YES;
[_recorder record];
_levelTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(levelTimerCallback:) userInfo:nil repeats:YES];
}
}
- (void)stop
{
[_recorder stop];
[_levelTimer invalidate];
_levelTimer = nil;
self.delegate = nil;
}
- (void)levelTimerCallback:(NSTimer *)timer
{
[_recorder updateMeters];
CGFloat db = [_recorder averagePowerForChannel:0] - DBOFFSET;
db += 120;
db = db < 0 ? 0 : db;
_db = db;
[self.delegate decibelNotifierUpdate:db];
}
#end
So, the first view controller that wants the notifications might have a property:
#property (strong, nonatomic) DecibelNotifier *dBNotifier;
You can initialize it with:
self.dBNotifier = [[DecibelNotifier alloc] init];
You can then turn on notifications with:
[self.dBNotifier startWithInterval:0.25 target:self];
So the question is when you turn these notifications on and off. I do it in viewWillAppear and viewWillDisappear. You can also pass the dbNotifier to other view controllers and they can get notifications, too.
Like I said, I tried this out and it works fine, with any view controller that wants to get notifications can just turn it on and specify itself as the delegate, and you're off to the races.
Related
I have production code which I see that it crashes once in a while with a EXC_BAD_ACCESS KERN_INVALID_ADDRESS on the block handler. I could for the life of me figure out what is wrong with my code. I have tried to reproduce this and could not in my controlled environment. Here is the stripped out and cleaned up code :
Code Snippet :
typedef void (^TestCallBackHandler)(NSString* location, NSError* error);
#interface _TestClass : NSObject
#property (nonatomic, copy) TestCallBackHandler handler;
#property (nonatomic, strong)NSTimer *endTimer;
- (void)fireOneOff:(TestCallBackHandler)handler;
#end
#implementation _TestClass
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
- (void)dealloc
{
NSLog(#"%# Dealloced",self);
_handler = nil;
}
- (void)_stop
{
NSLog(#"** Stopping ? %#",self);
if (_handler)
_handler([NSString stringWithFormat:#"Hello %#",[NSDate date]], nil);
}
#end
The calling code in my class is defined as :
#property (nonatomic, strong)_TestClass *testClassInstance;
and called like this :
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Few things to note :
The startTestClass can be called multiple times
The app wakes up in the background and this can be created.
Any pointers, help highly appreciated.. I just cant put a finger in this code and say that is what is wrong. Please help!
Two hints which occur a little bit strange to me:
First: why are you setting the object to nil in it's own block.
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Second your object may be released already and the NSTimer is trying to execute a method on a release object
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
I also dont get what the code does exactly and why. I believe there would be an easier and better maintainable solution for what you are achieving in this class.
If you want to have more information about EXC_BAD_ACCESS, then you can turn of NSZombies. In Xcode go to Product > Scheme > Edit Scheme and set checked Enable Zombie Objects.
I have an app where i want to create a temporary cache which stores key and value.I have done the following
My code is : IN appDelegate.h
#property (strong, nonatomic) NSMutableDictionary *articleCache;
In appDelegate.m
#synthesize articleCache;
and i am calling it in viewController.m
here i need to store the data so that it is cleared only when the app is terminated and is accessible anywhere in the app otherwise.
every time i visit an article i add it to the array so that next time i wont have to fetch it from the network thereby speed up the process.
the Problem is when i set the temp NSMutableDictionary the content gets added but for checkCache.articleCache i get nil.
#define DELEGATE ((AppDelegate*)[[UIApplication sharedApplication]delegate])
this is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//[self loadfeeds];
[self.activityIndi startAnimating];
AppDelegate *checkCache = DELEGATE;
NSString *link = self.webUrl;
//check if the article is already opened and cached before
if([[checkCache.articleCache allKeys] containsObject:link])
{
NSLog(#"Key Exists");
NSString *contents = [checkCache.articleCache valueForKey:link];
[self loadDataOnView:contents];
}
else
{
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock:^{
NSLog(#"Key not Exists");
[self startParsing];
}];
}
}
In parser method at the end i do the following i.e to store the article..
but if i add it directly to the checkCache.articleCache nothing is added what should i do?? but it gets added to temp.. do i access the articleCache incorrectly??
AppDelegate *checkCache = DELEGATE;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[checkCache.articleCache setObject:Content forKey:url];
[temp setObject:Content forKey:url];
So how can i solve it??
or Suggest me how can i use NSCache for the same problem. thanks a lot.
It might be a silly question but i m quite new to ios thanks.
In App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.articleCache = [NSMutableDictionary new];
return YES;
}
When you have to set the object in cache.
AppDelegate *checkCache = DELEGATE;
[checkCache.articleCache setObject:obj forKey:#"Key1"];
To get the object back:
AppDelegate *checkCache = DELEGATE;
id obj = [checkCache.articleCache objectForKey:#"Key1"];
Though there are better ways to get this done.
I'm currently trying make a queueHandler that takes an object array as input for executing drive commands on a simple Double robot. I'm currently trying to use GCD in order to serially execute my functions, but when I'm using dispatch_sync on my queue in won't wait until the NSTimer has run its course, but will continue to try and execute the commands from the next object in my array.
I have 3 functions, one which simply initializes an NSMutableArray(loadCommands) with 2 objects and runs the queueHandler, this is called when I toggle a switch. Then the queueHandler reads the variables from the objects(type, timing, queueNr) to determine what type of drive function will be executed and for how long. This I thought could be done in a switch statement, and I figured it would be great if the app could execute the function on the main thread(that is ok!) but it should wait until the NSTimer has run its course. I thought encapsulating the switch case with a dispatch_sync would solve this but it promptly skips to the next iteration in the loop and tries to execute the next function instead, which is drive backwards for 3 seconds.
When I test this with a single object in the array the command will be executed without trouble. I suppose I'm locking up the main thread somehow. Would perhaps waiting for a return value from the function in the #selector in the NSTimer statement help?
I've only played with Objective C for about 10 days, I'd appreciate any help I could get with this bit!
- (void)loadCommands {
//create an objectArray and put 2 objects inside it.
NSMutableArray *driveCommandsArray = [[NSMutableArray alloc] initWithCapacity:4];
//Command 1
DRCommands *C1 = [[DRCommands alloc] init];
C1.timing = 3;
C1.type = 1;
C1.queueNr = 1;
[driveCommandsArray addObject:C1];
//Command 2
DRCommands *C2 = [[DRCommands alloc] init];
C2.timing = 3;
C2.type = 2;
C2.queueNr = 2;
[driveCommandsArray addObject:C2];
//call queueHandler
[self queueHandler:driveCommandsArray];
}
Queue handler:
- (void)queueHandler: (NSMutableArray*) commandArray {
//Now, I'm not sure what I'm doing here, I watched a tutorial that
//solved a vaguely similar problem and he put a dispatch_async before the
//dispatch_sync. I can't run the dispatch_sync clause inside the case
//statement without this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Inside handler!");
unsigned long count;
count = [commandArray count]; //retrieve length/number of objects from the array.
unsigned long a;
for (a = 0; a < count;) {
//run the loop until all objects has been managed.
DRCommands* myObj = (DRCommands*)[commandArray objectAtIndex:a];
//create 2 serial queues.
dispatch_queue_t myQ1;
myQ1 = dispatch_queue_create("myQ1", NULL);
dispatch_queue_t myQ2;
myQ2 = dispatch_queue_create("myQ2", NULL);
int queueID = myObj.queueNr; //retrieve place in queue (not really used yet)
int timeID = myObj.timing; //retrieve the amount of time the command shall be run through the NSTimer
int typeID = myObj.type; //type of command
NSLog(#"Inside for loop!");
if (queueID == a+1) {
a++;
switch (typeID) {
{
case 1:
NSLog(#"inside case 1");
dispatch_sync(myQ1, ^{ //doesn't wait for NSTimer to finish,
//letting the Double drive forward for 3 seconds,
//before resuming operations.
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveForward) userInfo:nil repeats:YES];
});
break;
}
{
case 2:
NSLog(#"inside case 2");
dispatch_sync(myQ2, ^{
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveBackward) userInfo:nil repeats:YES];
});
break;
}
//add more cases
{
default:
break;
}
}
}
NSLog(#"Exited for loop, and count is %lu", a);
}
});
}
Drive commands:
//Go forward X seconds.
- (void)jDriveForward {
shouldDriveForward_ = YES; //sets a condition which is recognized by a callback function to run the device forward.
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveForward_ = NO;
}
}
//Go backwards X seconds.
- (void)jDriveBackward {
shouldDriveBackward_ = YES;
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveBackward_ = NO;
}
}
Provided drive function from the experimental API I'm using
I'm using a "token" such as "shouldDriveForward_" inside the function driveDoubleShouldUpdate which is TRUE for the duration of an NSTimer. I must call my drive methods inside that function for the robot not to default to idle mode. So whenever it is true for X duration, the function for driving forwards or backwards is active.
- (void)doubleDriveShouldUpdate:(DRDouble *)theDouble {
float drive = (driveForwardButton.highlighted) ? kDRDriveDirectionForward : ((driveBackwardButton.highlighted) ? kDRDriveDirectionBackward : kDRDriveDirectionStop);
float turn = (driveRightButton.highlighted) ? 1.0 : ((driveLeftButton.highlighted) ? -1.0 : 0.0);
[theDouble drive:drive turn:turn];
//below are custom functions
//The NSTimer I'm using keep the BOOL values below TRUE for X seconds,
//making the robot go forward/backward through this callback
//method, which I must use
if(shouldDriveForward_ == YES) {
[theDouble variableDrive:(float)1.0 turn:(float)0.0];
}
if(shouldDriveBackward_ == YES) {
[theDouble variableDrive:(float)-1.0 turn:(float)0.0];
}
}
You're kind of jumbled up here with the combination of GCD and NSTimer. There's nothing to say they can't be intermixed, but an all-GCD approach might be easier to get your head around. I think I've discerned the gist of what you're trying to do here, and hacked something together that might be helpful. I've put the whole project up on GitHub, but here's the meat of it:
#import "ViewController.h"
typedef NS_ENUM(NSUInteger, DRCommandType) {
DRCommandUnknown = 0,
DRCommandTypeForward = 1,
DRCommandTypeBackward = 2,
};
#interface DRCommand : NSObject
#property DRCommandType type;
#property NSTimeInterval duration;
#end
#implementation DRCommand
#end
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *commandNameLabel;
#property (weak, nonatomic) IBOutlet UILabel *secondsRemainingLabel;
#property (strong, atomic) DRCommand* currentlyExecutingCommand;
#property (copy, atomic) NSNumber* currentCommandStarted;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do an initial UI update
[self updateUI];
}
- (IBAction)loadCommands:(id)sender
{
DRCommand *C1 = [[DRCommand alloc] init];
C1.duration = 3.0;
C1.type = DRCommandTypeForward;
DRCommand *C2 = [[DRCommand alloc] init];
C2.duration = 3.0;
C2.type = DRCommandTypeBackward;
[self handleCommands: #[ C1, C2 ]];
}
- (void)handleCommands: (NSArray*)commands
{
// For safety... it could be a mutable array that the caller could continue to mutate
commands = [commands copy];
// This queue will do all our actual work
dispatch_queue_t execQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// We'll target the main queue because it simplifies the updating of the UI
dispatch_set_target_queue(execQueue, dispatch_get_main_queue());
// We'll use this queue to serve commands one at a time...
dispatch_queue_t latchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// Have it target the execQueue; Not strictly necessary but codifies the relationship
dispatch_set_target_queue(latchQueue, execQueue);
// This timer will update our UI at 60FPS give or take, on the main thread.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (1.0/60.0) * NSEC_PER_SEC, (1.0/30.0) * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ [self updateUI]; });
// Suspend the latch queue until we're ready to go
dispatch_suspend(latchQueue);
// The first thing to do for this command stream is to start UI updates
dispatch_async(latchQueue, ^{ dispatch_resume(timer); });
// Next enqueue each command in the array
for (DRCommand* cmd in commands)
{
dispatch_async(latchQueue, ^{
// Stop the queue from processing other commands.
dispatch_suspend(latchQueue);
// Update the "machine state"
self.currentlyExecutingCommand = cmd;
self.currentCommandStarted = #([NSDate timeIntervalSinceReferenceDate]);
// Set up the event that'll mark the end of the command.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cmd.duration * NSEC_PER_SEC)), execQueue, ^{
// Clear out the machine state for the next command
self.currentlyExecutingCommand = nil;
self.currentCommandStarted = nil;
// Resume the latch queue so that the next command starts
dispatch_resume(latchQueue);
});
});
}
// After all the commands have finished, add a cleanup block to stop the timer, and
// make sure the UI doesn't have stale text in it.
dispatch_async(latchQueue, ^{
dispatch_source_cancel(timer);
[self updateUI];
});
// Everything is queued up, so start the command queue
dispatch_resume(latchQueue);
}
- (void)updateUI
{
// Make sure we only ever update the UI on the main thread.
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
return;
}
DRCommand* currentCmd = self.currentlyExecutingCommand;
switch (currentCmd.type)
{
case DRCommandUnknown:
self.commandNameLabel.text = #"None";
break;
case DRCommandTypeForward:
self.commandNameLabel.text = #"Forward";
break;
case DRCommandTypeBackward:
self.commandNameLabel.text = #"Backward";
break;
}
NSNumber* startTime = self.currentCommandStarted;
if (!startTime || !currentCmd)
{
self.secondsRemainingLabel.text = #"";
}
else
{
const NSTimeInterval startTimeDbl = startTime.doubleValue;
const NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
const NSTimeInterval duration = currentCmd.duration;
const NSTimeInterval remaining = MAX(0, startTimeDbl + duration - currentTime);
self.secondsRemainingLabel.text = [NSString stringWithFormat: #"%1.3g", remaining];
}
}
#end
Let me know in a comment if there's any part you'd like more explanation on.
Note: The other answer here has the command doing a sleep; my approach is fully asynchronous. Which approach is right for you will depend on what your commands are actually doing which wasn't clear from the question.
You only need a single serial dispatch queue to which you will add your tasks.
I would start by defining a task class which implements your various commands - you can subclass if required.
DRCommand.h
#import <Foundation/Foundation.h>
#interface DRCommand : NSObject
#property uint duration;
-(void) dispatch;
#end
DRCommand.m
#import "DRCommand.h"
#implementation DRCommand
-(void)dispatch {
[self startCommand];
sleep(self.duration);
[self stopCommand];
}
-(void) startCommand {
NSLog(#"Override this method to actually do something");
}
-(void) stopCommand {
NSLog(#"Override this method to stop doing something");
}
#end
Then your run queue code will be something like
-(void) runQueue {
DRCommand *c1=[DRCommand new];
c1.duration=5;
DRCommand *c2=[DRCommand new];
c2.duration=7;
DRCommand *c3=[DRCommand new];
c3.duration=3;
NSArray *taskArray=#[c1,c2,c3];
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
for (DRCommand *command in taskArray) {
dispatch_async(queue, ^{
[command dispatch];
});
}
}
Note that you would have subclasses of DRCommand such as DRForwardCommand, DRBackwardCommand and so on, each with appropriate startCommand and stopCommand methods.
I am having a lot of trouble wrapping my head around the best way to use blocks. I am trying to retrieve pedometer data, and the method of accessing the data is a block...
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
[pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
});
}];
Using the above code I am able to get the data, log the data, and update the label on the screen, But I can't figure out how to set the data into an array or dictionary so I can do something else with it.
I understand why the arrays and dictionaries are always null... the blocks are running on a different thread and I am accessing them before the blocks have completed.
Can someone help me get through my head how to do something more with the data.
Update 1:
Right now I have this in .h
#property (strong, atomic) NSMutableDictionary *pedometerDictionary;
and I am synthesizing it in .m and I call this...
[self getNumbersForYesterday];
NSLog(#"Dictionary: %#", pedometerDictionary);
...which runs the above function and immediately tries to log the result. And like I said, I understand all the reasons it is NOT working. I just need to figure out how to change what i am doing to get it working.
Update 2:
This is in .h
#property (strong, atomic) NSMutableDictionary *pedometerDictionary;
and this is in .m
#synthesize pedometerDictionary;
- (id)init {
self = [super init];
if (self != nil) {
self.pedometerDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
and I am using it like this.
[self getNumbersForYesterday];
NSLog(#"Dictionary: %#", self.pedometerDictionary);
to call this.
- (void)getNumbersForYesterday {
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
[self.pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
});
}];
}
If I just wanted to keep all the work in the block I would be fine. What I have come to understand is that since blocks are asynchronous, I am trying to NSLog my dictionary, and the block isn't finished running yet. So, my dictionary is still NULL.
Dollars to donuts, your pedometerDictionary was never created in the first place (or it was, but the declaration isn't in a useful spot).
I.e. where is your line of code that says pedometerDictionary = [[NSMutableDictionary alloc] init];? And where is pedometerDictionary declared? How did you try to NSLog() values from it?
Also, use setObject:forKey:.
It is also odd that it is named pedometerDictionary. That is evidence that it is either declared as a global (which it shouldn't be), a local variable of whatever method contains the above code (which won't work), or you are declaring and using an instance variable directly.
The issue you are having is not a block timing issue, your dictionary should never be nil at worst it would contain no values.
You need to create your dictionary before using it. The appropriate place would be init method for most objects. If you are creating your object in Interface Builder then the method should be awakeFromNib.
To do something with the dictionary you can use an NSTimer or call a method from queryPedometerDataFromDate block handler. The use of #synchronized() directive is an example of how to keep access to the dictionary from overlapping at the same time in a threaded environment. This is not the case in this particular example as you are dispatching on the main thread and NSTimer also runs on the main thread. But should you go threaded #synchronized() would keep you from overlapping access.
#interface HelloWorld : NSObject
#property (retain, atomic) NSMutableDictionary *pedometerDictionary;
#property (retain, nonatomic) NSTimer *timer;
#end
#implementation HelloWorld
#synthesize pedometerDictionary, timer;
...
- (id)init {
self = [super init];
if (self != nil) {
self.pedometerDictionary = [NSMutableDictionary dictionary];
self.timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(doSomethingInterestingWithDictionary:) userInfo:nil repeats:YES];
}
return self;
}
or
- (void)awakeFromNib {
self.pedometerDictionary = [NSMutableDictionary dictionary];
self.timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(doSomethingInterestingWithDictionary:) userInfo:nil repeats:YES];
}
...
- (void)getNumbersForYesterday {
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
#synchronized (self) {
[self.pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
[self doSomethingInterestingWithDictionary:nil];
}
});
}];
}
// Will be called when queryPedometerDataFromDate returns and from a timer every 5 seconds.
- (void)doSomethingInterestingWithDictionary:(NSTimer *)aTimer {
#synchronized (self) {
NSLog(#"My days dictionary: %#", self.pedometerDictionary);
}
}
-(void) parseXML
{
[self performSelector:#selector(parseXML) withObject:self afterDelay:55.0 ];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://apikeygoeshere.com/data.xml"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *xmlString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSDictionary *xml = [NSDictionary dictionaryWithXMLString:xmlString];
NSMutableArray *items = [xml objectForKey:#"TeamLeagueStanding"];
NSMutableArray *newTeamObjectArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in items) {
TeamObject *myTeams = [TeamObject teamFromXMLDictionary:dict];
[newTeamObjectArray addObject:myTeams];
}
NSNull *nullValue = [NSNull null];
NSNull *nullValue2 = [NSNull null];
[newTeamObjectArray insertObject:nullValue atIndex:0];
[newTeamObjectArray insertObject:nullValue2 atIndex:1];
NSLog(#"standingsdataaaaa %#", newTeamObjectArray);
}
I want to add a unbutton to my storyboard so the user can refresh the data whenever he wants, but i don't him to be able to do this more than once per hour,
Can anyone help me? Thank you.
Just in the action method or wherever you call to get the XML
setEnabled: NO and set an NSTimer to fire nod a date that is 3600 seconds from now.
When it fires, setEnabled:YES
It might be nice to create a visual indicator to the user like a counter.
EDIT: In order to account for the fact that you still want to run the parseXML method every 55 seconds with or without the button press, I'm changing my answer by putting the conditional in the IBAction method triggered by the button press instead of putting the conditional in parseXML:
Declare an NSTimer as a class variable. For example, at the top of your .m directly after your #synthesizes, declare an NSTimer:
NSTimer *parseTimer;
Then in the IBAction method triggered by the button press, only call parseXML if the timer is nil; and if it is in fact nil and the parseXML method is going to run, initiate the timer so it doesn't run again for another hour:
- (IBAction)buttonPressed:(sender)id {
// If the parseTimer is active, do call parseXML.
// (And perhaps fire an alert here)
if (parseTimer != nil) return;
// Otherwise initialize the timer so that it calls the the method which
// will deactivate it in 60*60 seconds, i.e. one hour
parseTimer = [NSTimer scheduledTimerWithTimeInterval:60*60 target:self selector:#selector(reactivateButton) userInfo:nil repeats:YES];
[self parseXML];
}
The deactivateParseTimer method should deactivate the timer and set it to nil so that parseXML may run again:
- (void)deactivateParseTimer {
[parseTimer invalidate];
parseTimer = nil;
}