i want to remove some objects from a NSMutableDictionary after some Time.
I have:
//first Method start
PedObjectTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fireTimer:) userInfo:PedMessage repeats:YES];
[PedMessage setObject:PedObjectTimer forKey:#"Timer"];
[PedObjects setObject:[PedMessage copy] forKey:[PedMessage objectForKey:#"ObjectID"]];
//first Method end
- (void) fireTimer:(NSTimer*)theTimer {
NSLog(#"%#",PedObjects);
[PedObjects removeObjectForKey:[[theTimer userInfo]objectForKey:#"ObjectID"]];
NSLog(#"%#",PedObjects);
[theTimer invalidate];
theTimer = nil;
}
A problem is that i cant copy the Timer Object to the dictionary...
How can i delete the right one after 2 sec in the fireTimer method?
with this i get the same result , if i have 2 selectors it will not remove 2 entries in my dictionary...
[PedObjects setObject:[PedMessage copy] forKey:[PedMessage objectForKey:#"ObjectID"]];
[self performSelector:#selector(delete:) withObject:[PedObjects objectForKey:[PedMessage objectForKey:#"ObjectID"]] afterDelay:2.0];
-(void)delete:(NSMutableDictionary*)dict
{
NSLog(#"%#",[PedMessage objectForKey:#"ObjectID"]);
NSLog(#"%#",PedObjects);
[PedObjects removeObjectForKey:[dict objectForKey:#"ObjectID"]];
NSLog(#"%#",PedObjects);
}
You could use something like this:
Instead of a timer, you call
[self performSelector:#selector(delete:) withObject:yourDictionary afterDelay:2.0];
//delete implementation
-(void)delete:(NSMutableDictionary*)dict {
[dict removeObjectForKey:#"yourKey"];
}
Related
I am developing a game for class where a timer is ran when a button is pressed.
-(void)setTimer{
self->mTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(buttonNotPushed) userInfo: nil repeats:NO];
}
-(void)resetTimer{
[self->mTimer invalidate];
self->mTimer = nil;
Here is a snippet of how the timer code is used.
- (IBAction)yellowDot1:(id)sender {
if ([_labelColor.text isEqualToString:#"Yellow"]) {
[ self Scoring];
[self label];
[self resetTimer];
[self setTimer];
}
else ([self GameOver]);
}
- (IBAction)redDot1:(id)sender {
if ([_labelColor.text isEqualToString:#"Red"]) {
[ self Scoring];
[self label];
[self resetTimer];
[self setTimer];
}
else ([self GameOver]);
}
The game presents with a Play button which modals over to the next screen. Currently at 5 seconds, I would like to create a "difficult" mode where at the home screen, the user clicks on a "difficult" button and the timer for the game runs at 2 seconds instead. Right now, I am contemplating duplicating my storyboard and view controllers and going that way where I just make the timer a different interval. Is a shorter way possible through code for a difficult mode?
Simply declare a global a variable for this. Use a class (maybe a singleton) that holds the mode and use a -(float) getGameDuration; method
lets say...
// GameHandler.h
static GameHandler *sharedInstance = nil;
#interface GameHandler : NSObject
{
GameMode *mode;
}
typedef enum{
GAMEMODE_EASY = 0,
GAMEMODE_NORMAL,
GAMEMODE_HARD
} GameMode;
-(float) getGameDuration;
implementation
// GameHandler.m
+ (GameHandler *)sharedInstance // implement singleton
{};
-(float) getGameDuration
{
switch(mode)
{
case GAMEMODE_EASY:
return 10.f;
case GAMEMODE_NORMAL:
return 5.f;
case GAMEMODE_HARD:
return 2.f;
}
return 5.f;
}
// your other classes
float gameDurration = [[GameHandler sharedInstance] getGameDuration];
self->mTimer = [NSTimer scheduledTimerWithTimeInterval:gameDurration
target:self
selector:#selector(buttonNotPushed)
userInfo:nil
repeats:NO];
I am showing a count down timer using UILabel and NSTimer -
-(void)a_Method
{
[coolTimeLbl setNeedsDisplay];
coolTime = 5; // it is an int
coolingTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(cooling) userInfo:nil repeats:YES]; // NSTimer
}
-(void)cooling
{
if (coolTime>0)
{
coolTime = coolTime-1;
NSLog(#" coolTime----%#",coolTime);
coolTimeLbl.text =[NSString stringWithFormat:#"%d",coolTime];
NSLog(#" coolTimeLbl----%#",coolTimeLbl.text);
}
else
{
[coolingTimer invalidate];
coolingTimer = nil;
}
}
The first time everything works fine and I am getting coolTimeLbl.text as - 4 3 2 1 0
But the second time when I call aMethod, coolTimeLbl is not getting updated properly - it is like 3 2 0 etc (some weird behavior)
However both NSLogs (coolTime & coolTimeLbl) print perfectly all the times and values.
Why does this happen? I tried many ways like NSNotification etc.
Please help me to fix this.
If you're calling a_Method more than once before coolingTimer invalidates itself, the timer will tick more than once.
You should add some boolean like ;
BOOL isTimerValid;
in a_Method,
if(!isTimerValid)
{
isTimerValid = YES;
coolingTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(cooling) userInfo:nil repeats:YES]; // NSTimer
}
in cooling,
else
{
isTimerValid = NO;
....
}
Had the same issue in one of my viewControllers and another one was working OK with same NSTimer code. Looked at about 20 SO threads to get it solved. No luck. In my case
myLabel.opaque = false
solved it.
Don't ask me why.
I have one view controller (let's call it ViewController) with timer within its code, and have another view controller (let's call it ContentViewController) with webView within it which shows content depending on which button is clicked.
I have buttons on ViewController which, when clicked, pushes ContentViewController which loads a html file into its webView.
Timers are used to close ContentViewController if it is pushed.
Here's how I create timers:
-(void)createTimer :(NSNumber*)index
{
if (_show)
{
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:index, #"parameter1", nil];
switch ([index intValue])
{
case 1001:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1002:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1003:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1004:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
}
}
}
Here's the code in ViewController which closes ContentViewController when timer fired:
-(void)updateUi :(NSTimer *)timer
{
int index = [[timer.userInfo objectForKey:#"parameter1"] intValue];
if([self.navigationController.visibleViewController isKindOfClass:[ContentViewController class]])
{
if ([[[NSUserDefaults standardUserDefaults]valueForKey:#"CurrentString"] intValue]==index )
{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
And on web page which is shown on ContentViewController there's a button,when user clicks it ContentView must go back to ViewController. Here's how I do this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"coffee-drink"])
{
NSString *urlString = [[request URL] absoluteString];
NSArray *urlParts = [urlString componentsSeparatedByString:#":"];
if (urlParts.count > 1) ///------------поменял
{
[self.navigationController popViewControllerAnimated:YES];
}
}
return YES;
}
All of above works fine but sometimes it crashes with the following error:
[ContentViewController respondsToSelector:]: message sent to deallocated instance
And also, I have other errors like these:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
and
delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds
The last one appears very rarely.
I don't know what am I doing wrong.Can someone help me)?If needed any more information I will provide it!Thanks in advance!
Issue
You are popping your view without leaving ARC to deallocate all objects and delegates, by keeping them alive.
My Solution
For the NSTimer
At #implementation create NSTimer *timer and use it when you want to initialize it. To dealloc it correctly when you pop back, at viewWillDisappear set [timer invalidate] and timer = nil.
For delegates
Set you specific delegates to nil. For example self.delegate = nil
#Yuandra Ismiraldi is right. Your NSTimer will call your updateUI method every 20/10 seconds repeatedly untill it receives [timer invalidate]; instruction. You are getting this error messages as you navigate through your view controller tree. Sometimes your NavigationViewController will need more memory and will release some of un-used objects. In your case some of the previously shown view controllers that still runs the updateUI method every 20/10 seconds. Once this view controller has been released and your selector is called you will receive this crash.
I hope this explains why sometimes you get this crash, and sometimes not.
Your timer code
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES]
the repeat parameter is set to YES, this make your timer always repeat, thus repeating the sending of the message updateUI after the timer has run for the first time and the view has popped, thus resulting in your error. To make sure your timer only run once, set the repeat to NO
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:NO]
I have two methods that both create a random sprite node and add it to the scene. Let's call them spriteMethod1 and spriteMethod2.
I'd like to have a looping method that runs spriteMethod1, 5 times, and then spriteMethod2 once. There also needs to be a delay between each time the spriteMethods are called.
I thought the following might work, but it doesn't:
-(void) addObjects {
for (int i = 0; i < 5; i++) {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
}
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
I know this might not be the best solution, but it works for me:
-(void)addObjects {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:4];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:6];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:8];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:10];
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:13];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:18];
}
Add a timer in your interface:
#property (nonatomic, weak) NSTimer *timer;
schedule a timer somewhere in your code
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(spriteMethod1) userInfo:nil repeats:YES];
do this
int count = 0;
- (void)spriteMethod1 {
count ++;
// do your work here
if(count == 5) {
// stop the timer
[self.timer invalidate];
// call the other methods
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
}
The problem is that you won't see the delay because the selector is enqueued on the thread loop. From the documentation:
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
Another approach is to make the thread sleep for a few seconds.
[self spriteMethod1];
[NSThread sleepForTimeInterval:2.0f];
In this case your User Interface will hang if you don't execute this code in a separate thread.
This out the top of my head.. not sure if it'll do the trick:
- (void)startAddingObjects
{
NSNumber *counter = #(0);
[self performSelector:#selector(addMoreUsingCounter:)
withObject:counter
afterDelay:2];
}
- (void)addMoreUsingCounter:(NSNumber *)counter
{
int primCounter = [counter intValue];
if (primCounter < 5)
{
[self spriteMethod1];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:2];
}
else if (primCounter < 7)
{
[self spriteMethod2];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:3];
}
}
You'll probably still need to relate the delay to the counter to get the exact results you need.
I think your version doesn't work, because the "afterDelay" param is relative to the moment of invocation. You would need to multiply it with "i", in the for loop, and then use 13 and 18 respectively for the last two selectors.
Look into using an NSOperationQueue. You can set its maxConcurrentOperationCount to 1, to ensure it executes its actions sequentially. E.g.
NSOperationQueue * opQueue = [[NSOperationQueue alloc] init];
opQueue.maxConcurrentOperationCount = 1; // this ensures it'll execute the actions sequentially
NSBlockOperation *spriteMethod1Invoker = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; ++i)
{
[self spriteMethod1];
sleep(2); // sleep 2 secs between invoking spriteMethod1 again
}
}];
NSInvocationOperation *spriteMethod2Invoker = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(spriteMethod2) object:nil];
[opQueue addOperation:spriteMethod1Invoker];
[opQueue addOperation:spriteMethod2Invoker];
- (IBAction) goStrobe:(id) sender {
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(runLoop:)
userInfo:nil
repeats:YES];
}
- (void) runLoop {
if (imageTwo.hidden = YES) {
imageTwo.hidden = NO;
}
if (imageTwo.hidden = NO) {
imageTwo.hidden = YES;
}
}
My code is above. Every time I trigger goStrobe, it crashes and I can't figure out why. Any help would be greatly appreciated. Thanks
It's your runLoop function signature which is wrong in the selector, just remove the ":" at the end. You don't need this because your function does not take any parameters.