NemeSys
NemeSys

Reputation: 555

NSTimer, Threads. How to create a threaded NSTimer?

I'm developing a UIView where there must be a process that accesses to remote database and must update a UITableView with the retrieved results.

To do that I'm planning use a NSTimer that runs each 10 seconds the update process where database is accessed and data retrieved.

The problem is that if I run this in the main thread the view will be frozen till the data is retrieved and loaded.

Anyone knows wicht is the best way to do that? Any example will be apreciated.

Thanks.

EDIT ----

This is the code that I'm using to start & stop the update process:

-(void)startUpdating
{
    self.isUpdating = YES;

    timerLoop = [[NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:@selector(listMustBeUpdated:) userInfo:nil repeats:YES] retain];

    [[NSRunLoop mainRunLoop] addTimer: timerLoop
                              forMode: UITrackingRunLoopMode];


}
-(void)stopUpdating
{
    self.isUpdating = NO;

    [timerLoop invalidate];
    [timerLoop release];
    timerLoop = nil;
}

In addition, after a few minutes running the app chashes without error message. I think that is because memory usage or zombies, but I've tested with instruments and the memory usage was of 3.11 Mb(live) & no zombies.

Maybe is because the timer?

Thanks.

EDIT --- 2

Ok, now this is the code I'm using for the whole matter, and still freezing the UITableView while scrolls.

Maybe is because is updated from the thread and the scrolling is executed from the main thread?

-(void)loadMessagesFromUser:(int)userId toUserId:(int)userToId
{
    fromUserId = userId;
    toUserId = userToId;

    messages = [OTChatDataManager readMessagesFromUser:userId toUser:userToId];

    lastMessageId = [[messages getValueByName:@"Id" posY:[messages CountY]-1] intValue];

    [list reloadData];
}
-(void)messagesMustBeUpdated:(id)sender
{
    if ([NSThread isMainThread]) {
        [self performSelectorInBackground:@selector(updateMessages:) withObject:nil];
        return;
    }


}
-(void)updateMessages:(id)sender
{
    iSQLResult *newMessages = [OTChatDataManager readMessagesFromUser:fromUserId toUser:toUserId sinceLastMessageId:lastMessageId];

    for (int a=0;a<[newMessages CountY];a++)
    {
        [messages.Records addObject:[newMessages.Records objectAtIndex:a]];
        messages.CountY++;
    }

    lastMessageId = [[messages getValueByName:@"Id" posY:[messages CountY]-1] intValue];

    [list reloadData];

}
-(void)startUpdating
{
    self.isUpdating = YES;

    timerLoop = [[NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:@selector(messagesMustBeUpdated:) userInfo:nil repeats:YES] retain];

}
-(void)stopUpdating
{
    self.isUpdating = NO;

    [timerLoop invalidate];
    [timerLoop release];
    timerLoop = nil;
}

loadMessagesFromUser can be called from the mainThread and accesses the same nonatomic object. Is possible the quiet crash because this?

start and stop functions are called from the main thread. The rest is the timer stuff.

Thanks for the replies.

Upvotes: 0

Views: 2378

Answers (3)

rob mayoff
rob mayoff

Reputation: 386038

The first thing to consider is how you are accessing the remote database. If you are simply fetching the contents of a URL by HTTP, you can use NSURLConnection to fetch the contents asynchronously. You can set it up to use either a delegate or a completion block. It will perform the HTTP request and notify you when the response has arrived, and you don't need to worry about threads.

If you are using some library to access the remote database, and the library doesn't already provide an asynchronous API, then you need to worry about blocking the main thread. You can use Grand Central Dispatch (GCD) to avoid blocking the main thread.

In your view controller, you'll need a GCD queue, an NSTimer, and a flag to make sure you don't start a second database request if one is already running and taking a long time.

@implementation ViewController {
    dispatch_queue_t _databaseQueue;
    NSTimer *_databaseTimer;
    BOOL _databaseQueryIsRunning;
}

You need to clean up the GCD queue and the timer in your dealloc.

- (void)dealloc {
    if (_databaseQueue)
        dispatch_release(_databaseQueue);
    [_databaseTimer invalidate];
    [_databaseTimer release];
    [super dealloc];
}

You can start the timer when your view appears.

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _databaseTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self
        selector:@selector(databaseTimerDidFire:) userInfo:nil repeats:YES] retain];
}

When the view disappears, you can stop the timer.

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [_databaseTimer invalidate];
    [_databaseTimer release];
    _databaseTimer = nil;
}

When the timer fires, start a database request on the GCD queue if there isn't one running already.

- (void)databaseTimerDidFire:(NSTimer *)timer {
    if (_databaseQueryIsRunning)
        return;
    _databaseQueryIsRunning = YES;

    // Lazily create the GCD queue.
    if (!_databaseQueue)
        _databaseQueue = dispatch_queue_create("MyDatabaseQueue", 0);

    dispatch_async(_databaseQueue, ^{
        // This block runs off of the main thread.
        NSObject *response = [self performDatabaseRequest];

        // UI updates must happen on the main thread.
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self updateViewWithResponse:response];
        });
        _databaseQueryIsRunning = NO;
    });
}

Upvotes: 1

Parag Bafna
Parag Bafna

Reputation: 22930

I found a solution in this blog.

@interface Test : NSObject
{
     NSTimer *_timer;
     NSRunLoop *_runLoop;
     NSThread *_timerThread;
}

- (void)suspendTimer;
- (void)resumeTimer;
- (void)timerFireMethod:(NSTimer*)timer;

@end // End Test interface


@implementation Test

- (void)suspendTimer
{
     if(_timer != nil)
     { 
          [_timer invalidate];
          [_timer release];
          _timer = nil;

          CFRunLoopStop([_runLoop getCFRunLoop]);

          [_timerThread release];
          _timerThread = nil;
     }
}

- (void)_startTimerThread
{
     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

     _runLoop = [NSRunLoop currentRunLoop];
     _timer = [[NSTimer scheduledTimerWithTimeInterval:1.0
          target:self
          selector:@selector(timerFireMethod:)
          userInfo:nil
          repeats:YES] retain];
     [_runLoop run];
     [pool release];
}

- (void)resumeTimer
{
     if(_timer == nil)
     {
          _timerThread = [[NSThread alloc] initWithTarget:self
               selector:@selector(_startTimerThread)
               object:nil];
          [_timerThread start];
     }
}

- (void)timerFireMethod:(NSTimer*)timer
{
     // ...
}

@end // End Test implementation

I am also using this and its working fine for me.

Upvotes: 0

DougW
DougW

Reputation: 30055

Start the method your timer calls with this:

if ([NSThread isMainThread]) {
    // Must NOT be main thread
    [self performSelectorInBackground:@selector(listMustBeUpdated:) withObject:nil];
    return;
}

I don't notice anything in the code you've posted that would cause a crash with no output. I'd imagine it's in other code. If you want to ask about that, I'd suggest posting another question with more information about what exactly you're doing.

Upvotes: 1

Related Questions