I'm new in Objective C programming, I come from C++ and would better understand the ARC memory management; If I have the following situation:
-(void) test_method
{
NSTimer* t=[NSTimer ScheduledTimerWithTimeInterval:2
target:self selector;#selector(exec_method) userinfo:nil repeats:YES];
}
at the end of the method, I expected to lost the reference t, so, for the ARC, an automatic call to release and so deallocation of the NSTimer object, instead it seems that it still is in memory (the exec_method repeats its execution every 2 seconds)..or it will be deallocated when the system needs space in memory?
You do understand ARC correctly - this is just a slightly non-obvious case, because there is an a additional strong reference to your object that you cannot see. NSTimer is not behaving as expected because the fact that it is scheduled on the run loop means that it is retained there as well. So when your local goes away the object remains in memory.
ARC underlyingly uses a reference counting system - each object has a number (called the retain count) assigned to it, and only when that number reaches zero is the object released. When an object is created using alloc,copy,or new the retain count is set to 1. When the object is retained by another object the number increases, and when it is released it decreases (under the pre-ARC MRR system these were actual method calls made by the programmer - retain and release). ARC works in the same way, but just adds the same calls automatically at compile time).
So in this case the implicit call to release generated by ARC just reduces the count by 1 from 2 but since it does not reach zero the object is not released. Invalidating the timer will remove it from the runloop, and cause it to be deallocated.
From Apple docs:
Timers work in conjunction with run loops. To use a timer effectively,
you should be aware of how run loops operate—see NSRunLoop and
Threading Programming Guide. Note in particular that run loops
maintain strong references to their timers, so you don’t have to
maintain your own strong reference to a timer after you have added it
to a run loop.
You must invalidate an NSTimer to remove it from the run loop.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/
In order to simplify this process, what you can do is create two methods, one to create and start the timer and one to invalidate the time. These methods would require you to declare your times as IVARs.
Swift:
let timer = NSTimer(timeInterval: 1.0, target: self, selector: "incrementCompletedUnitCount:",
userInfo: nil, repeats: true)
progress.cancellationHandler = {
timer.invalidate()
}
progress.cancel()
Objective-C
NSTimer * _studentTimer1;
-(void)startStudentTimer {
NSLog(#"***TIMER STARTED***");
_studentTimer1 = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(dowork) userInfo:nil repeats:TRUE];
}
-(void)invalidateStudentTimer1 {
[_studentTimer1 invalidate];
}
Also, for safety, you may want to place your invalidation method inside the dealloc method of your view controller.
You may also consider extra safety measures by using a weak pointer to the timer like so:
NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:#selector(tick) userInfo:nil repeats:YES];
or as an IVAR:
NSTimer * __weak _studentTimer1;
And no, as for your last question, the time will remain in the run loop until you explicitly invalidate it, that's why you need to be careful with NSTimer and should wrap it up in as much safety as possible.
Related
I have been staring at my code for hours now so I thought I might try coming here for some fresh eyes. I needed to create a timer so I used the code below to do that. The first line is where I create the timer and the second part is my decrementTime method. This is in Objective C for an IOS app. This is my first time posting on StackOverflow (I usually find the answer I am looking for), so please let me know of any unwritten rules that I am not following.
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
- (void)decrementTime{
self.timeLeft--;
}
I'll add here where I invalidate the first timer
-(IBAction)infoClick:(id)sender{
[_timer invalidate];
}
Then here is info message, where I create another timer
- (void)hideInfoMessage {
_secondTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
}
clarification on my code: the order of events starts with my first code block (creating the timer). Then my second code block is called (invalidating the timer). then finally my third code block is called(making a new timer).
I know it is double incrementing because when I run the code I can visually see the timer double incrementing.
It's pretty easy to invoke the code that creates a timer twice. When you do that you actually have 2 timers running concurrently. Each one will decrement your value, so it will get decremented twice per second.
If you create a timer in your viewWillAppear method, for example, then you need to invalidate it in your viewWillDisappear method so you're sure you only have one running.
The same approach applies to other situations where you create a timer. You need to make sure you balance every call that creates a timer with a call that invalidates that timer.
If you use one of the scheduledTimer... methods, you can save a weak pointer to the timer. The run loop will retain it as long as it's running. When you invalidate it, the system run loop will release it and it will be deallocated. When that happens your weak pointer gets zeroed, so you don't even have to test it to see if it's valid/nil in your viewWillAppear method.
EDIT:
You need to instrument your code. In your infoClick method, is the variable _timer nil? What is it's address?
BTW, the target of an NSTimer is supposed to be a method that takes a single parameter, the timer itself. You should change your decrementTime method to look like this:
- (void) decrementTime: (NSTimer *) timer
{
NSLog(#"In method decrementTime, timer = %X", (unsigned long) timer)
self.timeLeft--;
}
Then look at your log and see if your decrementTime method is being called from 2 different timers (I would bet money that it is.)
You might also want to log the address of the timers you get back from your calls to scheduledTimerWithTimeInterval...
My requirement is that I want to call an API to ask for some new information from my server every 6 seconds,so I wrote my code as below:
MyBackgroundThread(){
while(self.isStop){
[self callMyAPI];
[NSThread sleepfortimeinterval : 6 ];
}
}
But I find out today that there is a way provided by Foundation library to write a run loop. So I can rewrite my code as below:
MyBackgroundThread(){
NSTimer *timer = [NSTimer timerWithTimeInterval:6 target:self selector:#selector(callMyAPI) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
while (! self.isCancelled) {
BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
However, I don't know if these is a better way to do my job then my original one? If it is, why? and how can I test the difference in efficiency(or other property?) between this two ways?
Thanks!
I think it's generally unnecessary to create new run loop for timer. I'd suggest one of two approaches:
Schedule NSTimer on main run loop, but have the called method then dispatch the request to background queue.
Create dispatch timer scheduled to run on designated background dispatch queue. To do that, create dispatch timer property:
#property (nonatomic, strong) dispatch_source_t timer;
and then instantiate and start dispatch timer source to run on your designated GCD queue:
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.polltimer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), kPollFrequencySeconds * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
<#code to be run upon timer event#>
});
dispatch_resume(self.timer);
There are times that creating a new run loop is useful, but it seems unnecessary in this simple scenario.
Having said that, it probably doesn't make sense to use a timer for initiating a network every six seconds. Instead, you probably want to start the next request six seconds after the prior one finishes. For a variety of reasons, your server might not be able to respond within six seconds, and you don't want concurrent requests to build up in these scenarios (which can happen if your requests run asynchronously).
So, I'd be inclined that the completion block of callMyAPI to do something as simple as:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), queue, ^{
<#code to issue next request#>
});
This obviates the need for timers (and custom run loops) entirely.
Finally, if you really need to detect system changes with that frequency, it might suggest a very different server architecture. For example, if you're polling every six seconds to see if something changed on the server, you might consider a sockets-based implementation or use push notifications. In both of those approaches, the server will tell the client apps when the significant event takes place, rather than the app behaving like Bart Simpson in the back seat of the car, constantly asking "are we there yet?"
The appropriate architecture is probably a function of with what frequency the server data is likely to be changing and what the client app requirements are.
I'm writing a custom file system cache component that has an index dictionary that represents important attributes about the files within the folder.
This is for an iOS app, and I'm writing in Objective-C
At various points in the implementation of adding objects / deleting object from the cache, the index dictionary needs to be saved to disk.
In order to stop this operation happening needlessly many times over, for example if objects are added to the cache in a for.. loop, I want to make a system that every time the dictionary is modified, a state is set to ensure that at some point in the future the dictionary will be saved. This should not happen immediately however, in case another change is made quickly, in which case the first 'save' operation should not happen, but another one should be queued up.
In pseudo-code:
//This is the method called by all other parts of the program whenever the dictionary is modified and it needs to be changed
-(void) dispatchSaveIndexDictionary {
//cancel any previous requests to save.
//queue up a save operation some short time later.
}
How I've implemented this:
-(void)saveIndexDictionaryDispatchDelayed
{
NSLog(#"dispatching index save");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(saveIndexDictionaryWriteToDisk) object:nil];
//Delay 0 means it gets queued up asap, which means the index dictionary on disk remains in sync whenever possible.
// - This is to solve the case of adding multiple objects in a for... loop
[self performSelector:#selector(saveIndexDictionaryWriteToDisk) withObject:nil afterDelay:0];
}
-(void)saveIndexDictionaryWriteToDisk
{
NSLog(#"Writing cache index to disk : %#", self.cachePath);
[NSKeyedArchiver archiveRootObject:self.indexDictionary
toFile:[OFMFileSystemCache indexDictionaryFullPathWithCachePath:self.cachePath]];
}
Using performSelector:withObject:afterDelay:0
I expected that this would always perform the 'write to disk' method AFTER any of the 'dispatch ' operations, i.e. we could have multiple write operations, if tasks took a long time, but that the 'write' operation would always be the last thing to happen.
I've seen from the logs that this does not always happen, if I do the simple use case of adding 10 files to the cache, then sometimes I get 'dispatching index save' happening and no call afterwards to 'Writing cache index to disk'. I don't really understand how this is possible!
Is there some reason why my implementation isn't a good idea (I guess there must be as it doesn't work very well)?
What do you think is a good secure design for this type of delayed method call, as it's critical that the index remains up to date with the contents of the cache. I.E. write to cache should always happen last, after all modifications have been made.
Thanks.
I've done something similar in my caches in the past. What I end up doing instead of using performSelector:afterDelay: is setup an NSTimer. Something like:
// elsewhere, setup an NSTimer* ivar called saveTimer
-(void) saveToDisk{
saveTimer = nil;
// actually save here
}
-(void)resetSaveTimer{
if(saveTimer) [saveTimer invalidate];
saveTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(saveToDisk) userInfo:nil repeats:NO];
}
-(void)doStuffWithCache{
// do stuff, add stuff, whatever
[self resetSaveTimer];
}
Depending on your threading, you may also want to add #synchronized(yourCacheDictionary){...} for each of the method bodies, just to make sure you're not trying to write to disk while also editing the dictionary, etc.
Following on from adam.wulf, I wanted to post the solution that I have finally settled on.
This uses NSTimer as suggested, because I had found inconsistent behaviours I couldn't explain with the 'NSObject performSelector:afterDelay:' approach.
After trying the timers approach I needed to modify the solution in some important ways.
1 - make sure the timer is dispatched on a queue with a properly set up runloop for timer execution. I'm using an external caching library for some caching operations, and this calls back on queues that are not set up appropriately. The easiest solution for me was to always dispatch the timer call on the main queue.
2 -Dispatch the actual write operation on a dedicated serial queue, so as not to block the main queue (where the timer method will fire as it is dispatched on that queue).
3 - As suggested wrapping the index Dictionary write to disk methods in #synchronized(indexDictionary) { } to ensure the contents are not modified while being written.
#pragma mark - Saving Index Dictionary
-(void)saveIndexDictionaryDispatchDelayed
{
dispatch_async(dispatch_get_main_queue(), ^{
if (_dispatchSaveTimer) {
[_dispatchSaveTimer invalidate];
_dispatchSaveTimer = nil;
}
_dispatchSaveTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(saveIndexDictionaryWriteToDisk)
userInfo:nil
repeats:NO];
});
}
-(void)saveIndexDictionaryWriteToDisk
{
dispatch_async(_cacheOperationQueue, ^{
#synchronized (self.indexDictionary) {
_dispatchSaveTimer = nil;
NSLog(#"Writing cache index to disk : %#", self.cachePath);
[NSKeyedArchiver archiveRootObject:self.indexDictionary
toFile:[OFMFileSystemCache indexDictionaryFullPathWithCachePath:self.cachePath]];
}
});
}
I have a method which displays a clock with seconds and the current time. This works fine except that this code will get called either half way through the current second or three quarters of the way through the current second depending on what time I open the app or run it. The method is called through the viewDidLoad method. When this happens my clock will be off up to almost 1 second. Is there any way to start my method when the next second start exactly? i.e. start it when the devices time is HH:MM:SS.000? Note: sorry if this is confusing with the excessive use of second and clock. I just mean I need to start my method at HH:MM:SS.000 (devices internal clock)
Using:
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds
target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo
repeats:(BOOL)repeats
With an object of NSTimer is probably the way to go.
Add the logic found in this StackOverflow question/answers and you should be able to get it right on an exact second. (Use the logic there to create an NSDate object with resolution to 1 second, then use that date in the method I mentioned above).
NSTimer *yourTimer = [[NSTimer alloc] initWithFireDate:nowToTheSecond
interval:1.0 target:self selector:#selector(updateClock) userInfo:nil
repeats:YES];
[[NSRunLoop mainLoop] addTimer:yourTimer forMode:NSRunLoopCommonModes];
NSTimer objects are not exact. They depend on the app visiting the event loop frequently, and can vary by 50 MS or more (according to what I've read in the docs). If I remember correctly they try to "snap back" to the desired time interval rather than drifting, but any given firing will not be exact.
That said, I guess what I would do is to take the current NSDate, convert it to an NSTimeInterval, take the ceiling value (the next higher whole number) and start a one-time timer that will fire at that moment. Then in the handler for that timer, start a once-a-second timer. Something like this:
//Get the current date in seconds since there reference date.
NSTimeInterval nowInterval =[NSDate timeInervalSinceReferenceDate];
//Figure out the next even second time interval.
NSTimeInterval nextWholeSecond = ceil(nowInterval);
//Figure out the fractional time between now and the next even second
NSTimeInterval fractionUntilNextSecond = nextWholeSecond - nowInterval;
//Start a one-time timer that will go off at the next whole second.
NSTimer oneTimeTimer = [NSTimer timerWithTimeInterval: fractionUntilNextSecond
target: self
#selector: (startSecondTimer:)
userInfo: nil
repeats: NO];
And the startSecondTimer method:
- (void) startSecondTimer: (NSTimer *)timer;
{
//Start a new, repeating timer that fires once per second, on the second.
self.secondsTimer = [NSTimer timerWithTimeInterval: 1.0
target: self
#selector: (handleSecondTimer:)
userInfo: nil
repeats: YES];
}
You should still calculate the new time in each call to your handleSecondTimer: method rather than relying on the number of times you are called, because if the system gets really busy at the moment when it's supposed to call your timer and can't get to you, it might skip a call completely.
Disclaimer: I haven't tried this, but it should work. My only concern is edge cases. For example, when the next whole second is too close to now and the one-time timer can't fire fast enough. It might be safer to add a second to the fractionUntilNextSecond value, so the second hand doesn't start running for greeter than 1 second but less than 2 seconds.
I have a doubt regarding the correct usage of NSRunLoop's runMode:beforeDate method.
I have a secondary, background thread that processes delegate messages as they are received.
Basically, I have process intensive logic that needs to be executed on a background thread.
So, I have 2 objects, ObjectA and AnotherObjectB.
ObjectA initializes AnotherObjectB and tells AnotherObjectB to start doing it's thing. AnotherObjectB works asynchronously, so ObjectA acts as AnotherObjectB's delegate. Now, the code that needs to be executed in the delegate messages, needs to be done on a background thread. So, for ObjectA, I created an NSRunLoop, and have done something like this to set the run loop up:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (aCondition);
Where aCondition is set somewhere in the "completion delegate message".
I'm getting all my delegate messages and they are being processed on that background thread.
My question being: is this the correct approach?
The reason I ask this is because [NSDate distantFuture] is a date spanning a couple of centuries. So basically, the runLoop won't timeout until "distantFuture" - I definitely won't be using my Mac or this version of iOS till then. >_<
However, I don't want the run loop to run that long. I want the run loop to get done as soon as my last delegate message is called, so that it can properly exit.
Also, I know that I can set repeating timers, with shorter intervals, but that is not the most efficient way since it's akin to polling. Instead, I want the thread to work only when the delegate messages arrive, and sleep when there are no messages. So, is the approach I'm taking the correct approach, or is there some other way of doing it. I read the docs and the guide, and I set this up based off what I understood from reading them.
However, when not completely sure, best to ask this awesome community for an opinion and confirmation.
So, thanks in advance for all your help!
Cheers!
The code is in the docs:
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO somewhere else in the program.
After your last "message", un-set shouldKeepRunning (on the same thread as the run loop!) and it should finish. The key idea here is that you need to send the run loop an event so it knows to stop.
(Also note that NSRunLoop is not thread-safe; I think you're supposed to use -[NSObject performSelector:onThread:...].)
Alternatively, if it works for your purposes, use a background a dispatch queue/NOperationQueue (but note that code which does this shouldn't touch the run loop; things like starting a NSURLConnection from a dispatch queue/NSOperationQueue worker thread will likely cause problems).
The reason I ask this is because [NSDate distantFuture] is a date spanning a couple of centuries.
The method runMode:beforeDate: will
return NO immediately if there are no sources scheduled on the RunLoop.
return YES whenever an event has been processed.
return YES when the limitDate has been reached.
So even if the limitDate is very high, it will return after every processed event, it will not keep running until limitDate has been hit. It would only wait for that long if no event is ever processed. limitDate is thus like a timeout after that the method will give up on waiting for an event to take place. But if you want to have multiple events in a row handled, you must call this method over and over again, hence the loop.
Think of fetching packets with timeout from a network socket. The fetch call returns when a packet arrives or when the timeout has been hit. Yet if you want to process the next packet, you must call the fetch method again.
The following is unfortunately pretty bad code for two reasons:
// BAD CODE! DON'T USE!
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) {
[runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
}
If no RunLoopSource is yet scheduled on the RunLoop, it will waste 100% CPU time, as the method will return at once just to be called again and that as fast as the CPU is able to do so.
The AutoreleasePool is never renewed. Objects that are autoreleased (and even ARC does that) are added to the current pool but are never released as the pool is never cleared, so memory consumption will raise as long as this loop is running. How much depends on what your RunLoopSources are actually doing and how they are doing it.
A better version would be:
// USE THIS INSTEAD
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) #autoreleasepool {
BOOL didRun = [runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
if (!didRun) usleep(1000);
}
It solves both problems:
An AutoreleasePool is created the first time the loop runs and after every run it is cleared, so memory consumption will not raise over time.
In case the RunLoop didn't really run at all, the current thread sleeps for one millisecond before trying again. This way the CPU load will be pretty low since as as no RunLoopSource is set, this code only runs once every millisecond.
To reliably terminate the loop, you need to do two things:
Set keepRunning to NO. Note that you must declare keepRunning as volatile! If you don't do that, the compiler may optimize the check away and turn your loop into an endless loop since it sees no code in the current execution context that would ever change the variable and it cannot know that some other code somewhere else (and maybe on another thread) may change it in the background. This is why you usually need a memory barrier for these cases (a lock, a mutex, a semaphore, or an atomic operation), as compilers don't optimize across those barriers. However, in that simple case, using volatile is enough, as BOOL is always atomic in Obj-C and volatile tells the compiler "Always check thes value of this variable as it may change behind your back without you seeing that change at compile time".
If the variable has been changed from another thread and not from within an event handler, your RunLoop thread may be sleeping inside the runMode:beforeDate: call, waiting for a RunLoopSource event to take place which may take any amount of time or never happen at all anymore. To force this call to return immediately, just schedule an event after changing the variable. This can be done with performSelector:onThread:withObject:waitUntilDone: as shown below. Performing this selector counts as a RunLoop event and the method will return after the selector was called, see that the variable has changed and break out of the loop.
volatile BOOL keepRunning;
- (void)wakeMeUpBeforeYouGoGo {
// Jitterbug
}
// ... In a Galaxy Far, Far Away ...
keepRunning = NO;
[self performSelector:#selector(wakeMeUpBeforeYouGoGo)
onThread:runLoopThread withObject:nil waitUntilDone:NO];