How to identify Nsoperation dynamically.
I am creating a NSoperation subclass
- (id)initWithConnectDevice:(ConnectDevice *)cDevice toPeripheral:(CBPeripheral*)peripheral oPerationIndex:(int) index{
if (self = [super init]) {
operationIndex = index;
executing = NO;
finished = NO;
self.connectDevice = cDevice;
[self.connectDevice setDelegate:self];
self.connectedPeripheral = peripheral;
}
return self;
}
-(BOOL)isConcurrent{
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
-(void) terminateOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)start {
#autoreleasepool {
if (self.isCancelled){
[timer invalidate];
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
-(void)timerFired:(id)sender{
NSLog(#"timerFired");
}
I am scanning for BLE devices. For example I will found 3 devices, 3 buttons will create.
I am calling this class every time when I am clicking on that button.That means, When I click on button, I am connecting to bluetooth device and get the data from that device for every second thats why I am using timer in start method.
Like that I have multiple bluetooth devices, whenever I clicked on button, I want to create multiple instances of Operation Queue class.
Now, I want to identify which data is coming from which thread.
Could you please help me....
This the way I am calling above class from viewcontroller
OperationQueue *queue = [[OperationQueue alloc] initWithConnectDevice:connectDevices toPeripheral:peripheral oPerationIndex:operationIndex];
queue.delegate = self;
[[[AppDelegate app] mainQueue] addOperation:queue];
operationIndex = operationIndex+1;
Each of your operations has two identifying properties already - the connected device and the index. When the timer fires, depending what you want to do with the data, you can use these properties to tell where the data is coming from.
You can either have a delegate property on the operation, where a delegate method is called when the timer fires that takes the device and the received data as parameters, or you the operation could have a block property, which takes a block to be executed whenever data is received - the block would have the device and the received data as parameters.
Assuming you want to update the UI when the data is received, be sure to call the delegate method or execute the block on the main thread.
Related
My timer does not stop even if i am doing "invalidate" and "nil" after reading other links. My code is as below:
#property(nonatomic,strong) NSTimer *mytimer;
- (void)viewDidLoad {
[self performSelectorOnMainThread:#selector(updateProgressBar:) withObject:nil waitUntilDone:NO];
<do some other work>
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
self.DownloadProgressBar.progress= (float)count/10.0f;
}
else{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
}
1) The timer goes on infinetly even when invalidating timer else condition is hit after count >10 and count keeps on incrementing.
2) i want to do this on a non-main thread . i want to continue in viewdidload() after starting the timer. How to do this ?
I visited other links on SO, all i understood was to call invalidate and nil on timer pointer. I am still facing problems. Could anyone tell me what i am missing here and what i can i do to run the updateProgressBar on background thread and update the progress bar ?
don't need to schedule a timer each time, schedule it once and timer will fire every second for example u can do like below,
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorOnMainThread:#selector(startTimerUpdate) withObject:nil waitUntilDone:NO]; //to start timer on main thread
}
//hear schedule the timer
- (void)startTimerUpdate
{
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
//self.DownloadProgressBar.progress= (float)count/10.0f;
NSLog(#"progress:%f",(float)count/10.0f);
}
else
{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
}
}
I think you are scheduling timer multiple time. I think 10 time. just schedule time one time or if require many time then invalidate it that many time as schedule.
Update according to comment : Schedule timer from viewdidload and addobserver means notification on task. when your task will completed invalidate timer. and update your progress in selector method of timer so when you invalidate it it will automatically stop progress bar.
Second thing : you should invalidate timer before moving another viewcontroller also because this objects remains live untill invalidate.
Hope this will hellp :)
I want to maintain timer with multiple UIViewControllers.
Like, I am creating one timer in ViewController1 for 60 second interval.
after 20 second, application navigates to ViewController2.(so 40 second is remaining to execute timer).
After 20 second stay i come back to ViewController1, at this time timer should execute after 40 second of come back and then after it will execute after 60 second.
So how can I do this?
Thanks in advance.
If you want to use one item across several instances, try it with the singleton design pattern. But like it seems, you never navigate back from your VC1, so all obejects are still there.
On the base of How to Pause/Play NSTimer? you can change some parts to make it fitting your case.
- (void)stopTimer
{
if( timer )
{
[timer invalidate];
[timer release]; // if not ARC
timer = nil;
}
}
- (void)startTimer
{
[self stopTimer];
int interval = 1.;
timer = [[NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(countUp)
userInfo:nil repeats:YES] retain]; // retain if not ARC
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self timerStart];
}
-(void)viewWillDisappear:(BOOL)animated
{
[self stopTimer];
[super viewWillDisappear:animated];
}
-(void)countUp
{
if(timePassed++ >= 60) // defined in header
{
timePassed=0;
[self myCustomEvent];
}
}
Although it may be a solution with better practices, you can set the NSTimer inside your AppDelegate and make the AppDelegate manage segues to push or pop your UIViewControllers
I need to create a routine that save automatically a file content in a constant time period, ie, a backgroung loop that perform the save instructions. I thinked in use a recursive call of performSelector like below:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self performSelector:#selector(saveMethod) withObject:nil afterDelay:kTimeConstant];
}
- (void)saveMethod{
//The save logic should to be here
[self performSelector:#selector(saveMethod) withObject:nil afterDelay:kTimeConstant];
}
It works, but when I get out of viewController, it still running, and it must to stop.
are there any better way to execute it? Thank you!
This is probably a better implementation:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Start timer and sets it to a property called saveTimer
self.saveTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(saveMethod:)
userInfo:nil
repeats:YES];
}
- (void)saveMethod:(NSTimer*)theTimer {
// The save logic should to be here
// No recursion
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Stop timer
[self.saveTimer invalidate];
}
This is running on the main thread so it is probably not the best implementation but it should work better than what you currently have.
There is a function NSRunLoop cancelPreviousPerformRequestsWithTarget:selector:object: which allows you to cancel the performSelector call. Call this when you unload the view controller
ie.
[NSRunLoop cancelPreviousPerformRequestsWithTarget:self selector:#selector(saveMethod) object:nil];
I have a function drawView which is thread safe and does drawing for short periods of game animation. I have functions startAnimating and stopAnimating. I want a background thread to call drawView at a regular rate but only during the period that the animation is enabled.
In startAnimating I was going to call the view's performSelectorInBackground:withObject: to get the thread running.
I'm a little confused about how to do the thread communication and initialize the drawing thread: specifically, setting up a runloop to receive display link messages and then at the end notifying the thread that it should exit and exiting the run loop cleanly when stopAnimating is called from the main thread. I want to ensure that drawView is never called after stopAnimating, and also that the drawing thread is not cancelled abruptly in the middle of the drawing operation. I have seen a lot of very poor answers to this kind of question on line.
OK after reading the Apple pages all evening, I finally solved it with this code:
// object members
NSThread *m_animationthread;
BOOL m_animationthreadrunning;
- (void)startAnimating
{
//called from UI thread
DEBUG_LOG(#"creating animation thread");
m_animationthread = [[NSThread alloc] initWithTarget:self selector:#selector(animationThread:) object:nil];
[m_animationthread start];
}
- (void)stopAnimating
{
// called from UI thread
DEBUG_LOG(#"quitting animationthread");
[self performSelector:#selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO];
// wait until thread actually exits
while(![m_animationthread isFinished])
[NSThread sleepForTimeInterval:0.01];
DEBUG_LOG(#"thread exited");
[m_animationthread release];
m_animationthread = nil;
}
- (void)animationThread:(id)object
{
#autoreleasepool
{
DEBUG_LOG(#"animation thread started");
m_animationthreadrunning = YES;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
CADisplayLink *displaylink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayLinkAction:)];
[displaylink setFrameInterval:3];
[displaylink addToRunLoop:runLoop forMode:NSDefaultRunLoopMode];
while(m_animationthreadrunning)
{
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DEBUG_LOG(#"runloop gap");
}
[displaylink removeFromRunLoop:runLoop forMode:NSDefaultRunLoopMode];
DEBUG_LOG(#"animation thread quit");
}
}
- (void)quitAnimationThread
{
DEBUG_LOG(#"quitanimationthread called");
m_animationthreadrunning = NO;
}
- (void)displayLinkAction:(CADisplayLink *)sender
{
DEBUG_LOG(#"display link called");
//[self drawView];
}
The reason I use the line [self performSelector:#selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO] and not simply set m_animationthreadrunning = NO in stopAnimating is because the run loop may not return in a timely fashion, but calling a selector forces it to return.
the question is simple : my app control if there is an update every time it starts. If there is an update a popup will be shown with a Yes or No choose. When user tap Yes 4 methods start. These methods download xml file and upload CoreData. This is the code of the alert :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
But there is a problem : when user tap Yes the alert doesn't disappear and remain on screen until all methods are finished. So i try this other code :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[NSThread detachNewThreadSelector:#selector(startDownloads) toTarget:self withObject:nil];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
-(void)startDownloads {
NSInvocationOperation *opPoi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdatePoi) object:nil];
NSInvocationOperation *opItinerari=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateItinerari) object:nil];
NSInvocationOperation *opArtisti=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateArtisti) object:nil];
NSInvocationOperation *opEventi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateEventi) object:nil];
NSArray *operations=[[NSArray alloc] initWithObjects:opPoi,opItinerari,opArtisti,opEventi, nil];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
[queue addOperations:operations waitUntilFinished:YES];
[queue waitUntilAllOperationsAreFinished];
}
There is a problem even here : i tap start, but the activity viewer doesn't appear. The alert disappear and the thread start and run the 4 methods one after another.
I need the processes run in background, just like happened with my 2nd code, but i need even my showActityViewer method will be run and show the spinner.
Thanks :)
First things first. You don't need to start 4 operations, since you are already in a secondary thread and do not need that the 4 operations be executed in parallel. You could simply do:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[pool release];
}
Above all, you need define an autorelease pool in startDownloads if you use autorelease in the downloadControl* methods, otherwise I suspect you will have leaks.
As to why the activity indicator does not show up, it depends on the fact that you are calling:
[self hideActivityViewer];
immediately after detach. So, you are showing it and removing it, before event the UI has got the time to update itself. Remove that line from there and rewrite startDownloads like this:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self performSelectorOnMainThread:#selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
[pool release];
}
Here notice that I am calling into the main thread to hideActivityViewer because only the main thread can safely use UIKit.
EDIT:
I did not know that you were using Core Data in the download methods...
Have a look at Concurrency with Core Data. You will need to tweak a bit your code by at least using a separate managed object context for your secondary thread (I don't know if it is feasible for you to create the moc there).
Also have a look at this tutorial from Cocoa is my Girlfriend.
As an alternative to all that, you could consider doing:
if (buttonIndex==1) {
[self showActivityViewer];
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
with:
-(void)startDownloads {
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
}
This does not use threads at all, but I am not sure that the activity viewer will show without any glitch. One more level of hack, if needed, and you could specify a delay in
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0.1];