Can't access NSTimer's userInfo - objective-c

I have this code.
- (void)scheduleTimerAfterDelay:(NSTimeInterval)delay {
dispatch_async(dispatch_get_main_queue(), ^{
_timer = [NSTimer scheduledTimerWithTimeInterval:delay
target:self
selector:#selector(triggerTimer:)
userInfo:[NSString stringWithFormat:#"%f", delay]
repeats:NO];
});
}
- (void)triggerTimer:(NSTimer *)timer {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Triggered timer after %# s.", _timer.userInfo); // <-- Exception thrown!
// Do stuff
});
}
But when the timer triggers, _timer.userInfo causes a Exception: EXC_BAD_ACCESS (code=1, address=0xc)).
What have I missed here? Printing _timer at a breakpoint on the line of the exception shows that _timer is <__NSCFTimer: 0x14ec8cb0>. But I can't access userInfo via lldb either. I'm using ARC.

The userInfo should be a dictionary:
_timer = [NSTimer scheduledTimerWithTimeInterval:delay
target:self
selector:#selector(triggerTimer:)
userInfo:#{ #"name" : #"Zinedine Zidane",
#"age" : #42 }
repeats:NO];
and you obviously need to change the way you access it in the selector:
You need to retain the userInfo before calling dispatch_async():
- (void)triggerTimer:(NSTimer *)timer {
NSString *s = timer.userInfo; // Strong reference!
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Triggered timer after %# s.", s);
// Do stuff
});
}

Related

Calling a method every second doesn't work

I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop

NSTimer Repeats not working for me

Problem, runs once.
-(void)firingLogicForPlayer:(Player *)player {
if (player.playerTargetLock) {
if (!_fireRateTimer) {
_fireCounter = 0;
_fireRateTimer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(burstControl:)
userInfo:player.name
repeats:YES];
[_fireRateTimer fire];
BOOL timerState = [_fireRateTimer isValid];
NSLog(#"Timer validity is: %#", timerState?#"YES":#"NO");
}
}
}
-(void)burstControl:(NSTimer *)theTimer {
NSLog(#"burstControl Initiated");
NSString *playerName = (NSString *)[theTimer userInfo];
Player *player = (Player *)[self childNodeWithName:playerName];
if (_fireCounter < 5) {
[self playerBeginFiring:player];
_fireCounter++;
} else {
NSLog(#"this ran to kill timer");
[_fireRateTimer invalidate];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self firingLogicForPlayer:player];
});
}
}
2015-09-16 13:37:44.964 ***[20592:3367845] burstControl Initiated
2015-09-16 13:37:44.974 ***[20592:3367845] Timer validity is: YES
2015-09-16 13:37:45.147 ***[20592:3367845] hit made
This is what logs, how the logic works is, firingLogic is initialised on a target lock. So the timer should run 5 times before being invalidated because of the _fireCounter counter. timer begins burst control, checks firecounter if firecounter is < 5 it fires a bullet, increases the firecounter. if the firecounter > 5 it invalidates the timer, and dispatches it to run again after 1.5 seconds.
However, the problem is that the timer is only running once. Yet, it's valid after the initial fire. Pretty confused.
You have to add it to the NSRunLoop. Otherwise you can use + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
Please check below code
if (!_fireRateTimer) {
_fireCounter = 0;
_fireRateTimer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(burstControl:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_fireRateTimer forMode:NSDefaultRunLoopMode];
BOOL timerState = [_fireRateTimer isValid];
NSLog(#"Timer validity is: %#", timerState?#"YES":#"NO");
}
Thanks :)

After closing the App NSTimer updating in iphone

When my app become active I get last image from ALAssetsLibrary which is stored in sqlite and I start a NSTimer to check a new image is added to ALAssetsLibrary. After screenshot is taken in my app it displays alert because image name in sqlite and ALAssetsLibrary last image name are mismatching. When app is closed I stop timer and user open it again I start the timer again to check screenshot image is deleted or not. If it is not deleted it will display an alert
-(void)getAllImagesName
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSMutableArray *imgArray = [[NSMutableArray alloc]init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just photos.
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
for (int i=0; i<[group numberOfAssets]; i++) {
// Chooses the photo at the last index
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:i] options:0 usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
// ALAssetRepresentation *representation = [alAsset defaultRepresentation];
[imgArray addObject:alAsset.defaultRepresentation.filename];
}
}];
}
NSString *img;
img=[[DBModel database]getPreviousName];
NSLog(#"select img=%#",img);
NSArray *results;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#",img];
results = [contentArray filteredArrayUsingPredicate:predicate];
//NSLog(#"predicate results = %#",results);
if ([results count] != 0) {
[self displayAlertMsg];
}
else{
}
}
} failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
//NSLog(#"No groups");
}];
}
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(getAllImagesName) userInfo:nil repeats:YES];
runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer1 forMode:NSRunLoopCommonModes];
[runLoop run];
My problem is after user taken screen shot in other app and try to use my app it is displaying an alert. Is timer running after closing app? please let me know anyone not understanding my problem.

NSMutableDictionary with NSTimers

There are several recipes in my CoreData. Every recipe has attributes prepHour and prepMin. I would like by tapping to button Start Timer start countdown timer. But there can be several timers (for example for each recipe) which must work simultaneously
I selected this way (may be it's wrong):
For timers I used singleton Timers.
By tapping to my Start Timer I call method with parameters (name, prepHour, prepMin)
My startTimer method creates timer and puts it to NSMutableDictionary
Calling method:
[[Timers sharedTimers] startTimer:self.recipe.name startTimer:self.recipe.prepHour startTimer:self.recipe.prepMin];
In Timers singleton:
- (void)startTimer:(NSString *)timerName startTimer:(NSNumber *)hVal startTimer:(NSNumber *)mVal
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:#selector(tick:) userInfo:nil repeats:YES];
if ([timer isValid]) {
NSArray *array = [NSArray arrayWithObjects: timer, hVal, mVal, nil];
if (_timerDict == NULL) {
_timerDict = [[NSMutableDictionary alloc] init];
}
[_timerDict setObject:array forKey:timerName];
NSLog(#"%#", _timerDict);
}
}
- (void)stopTimer:(NSString *)timerName
{
NSArray *array = [_timerDict objectForKey:timerName];
NSTimer *timer = [array objectAtIndex:0];
if ([timer isValid]) {
[timer invalidate];
[_timerDict removeObjectForKey:timerName];
}
}
- (void)tick:(NSString *)timerName
{
NSArray *array = [_timerDict objectForKey:timerName];
//NSTimer *timer = [array objectAtIndex:0];
NSInteger hVal = [[array objectAtIndex:1] integerValue];
NSInteger mVal = [[array objectAtIndex:2] integerValue];
NSInteger sVal = 0;
How should I use tick method for each timer? I would like each timer call this method with own parameters (timer, prepHour, prepMin)
This may be a matter of taste, but I don't use timers to remember an expiration time, I use them to pulse at a frequency. For your app, I'd use one timer at the lowest common denominator frequency, like one second. Instead of a singleton, I have a single timer, that the app delegate can invalidate and restart.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(tick:) userInfo:nil repeats:YES];
Then keep each expiration as one property on each recipe. For example:
- (IBAction)pressedStartButtonForRecipeA:(id)sender {
// this is a NSDate * property
self.recipeADone = [NSDate dateWithTimeIntervalSinceNow:90*60]; // ninety minutes
}
- (void)tick:(NSTimer *)timer {
NSDate *now = [NSDate date];
NSTimeInterval secondsUntilAIsDone = [self.recipeADone timeIntervalSinceDate:now];
if (secondsUntilAIsDone <= 0.0) {
// update UI to say that A is done
} else {
// update UI to reduce countdown
}
// and so on for all recipes
}
This sounds like a poor use of a singleton. I would have…well, one timer per timer if you want to go with this design. Alternatively, you could have one timer and just calculate the offset from each timer's start when it ticks. (Which approach makes sense depends on your particular app. I'd usually go with the second, but YMMV.)
Also, timers do not pass an NSString to their callback; they pass themselves. To allow the callback to get some information from the timer, you can set the info its the timer's userInfo dictionary.

Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)
+ (ALAsset*) assetForPhoto:(Photo*)photo
{
ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
__block ALAsset* assetToReturn = nil;
NSURL* url = [NSURL URLWithString:photo.assetUrl];
NSLog(#"assetForPhoto: %#[", url);
[library assetForURL:url resultBlock:^(ALAsset *asset)
{
NSLog(#"asset: %#", asset);
assetToReturn = asset;
NSLog(#"asset: %# %d", assetToReturn, [assetToReturn retainCount]);
} failureBlock:^(NSError *error)
{
assetToReturn = nil;
}];
NSLog(#"assetForPhoto: %#]", url);
NSLog(#"assetToReturn: %#", assetToReturn); // Invalid access exception coming here.
return assetToReturn;
}
The problem is assetToReturn gives an EXC_BAD_ACCESS.
Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.
A few things:
You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.
The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.
- (ALAsset *)assetForURL:(NSURL *)url {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
result = [asset retain];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = [error retain];
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
[assetError release];
return [result autorelease];
}
You should retain and autorelease the asset:
// ...
assetToReturn = [asset retain];
// ...
return [assetToReturn autorelease];