0

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.

NemeSys
  • 555
  • 1
  • 10
  • 28
  • Duplicate? http://stackoverflow.com/questions/4949438/thread-and-nstimer – Almo Feb 28 '12 at 18:19
  • I reed this post before to ask, and the whole list of possible related links that loads when the title is filled. I tried this solution but the application still freezing when ask database :(. Thanks for the reply. – NemeSys Feb 28 '12 at 18:39
  • You don't need to add the timer to the run loop; that's done for you by `scheduledTimerWithTimeInterval:...` – jscs Feb 28 '12 at 18:56
  • Sure, just checking user1132003. :) – Almo Feb 28 '12 at 19:00
  • "loadMessagesFromUser can be called from the mainThread and accesses the same nonatomic object. Is possible the quiet crash because this?". Yes. Use atomic properties. Even then, you can potentially have issues if you are not careful. – DougW Feb 28 '12 at 21:42
  • Hi DougW, thanks for the response. But which elements must be atomic, the object where the data is stored and the UITableview? Or only the object with data? – NemeSys Feb 29 '12 at 10:26

3 Answers3

1

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.

DougW
  • 28,776
  • 18
  • 79
  • 107
1

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;
    });
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks for the detailed response, but modify the DAL classes is not an option. The access is a remote MS SQL Server 2008 Database across a web service API. The connection is not asynchronous, because in most of the cases the flow of the program must continue after receive the data. – NemeSys Feb 28 '12 at 19:19
  • I never mentioned modifying the database access layer. Put all your calls to the DAL API in the `performDatabaseRequest` method. – rob mayoff Feb 28 '12 at 19:21
  • Ok, but if apply this what happens with the second request to database? I need to execute both queries with the first condition in databaseTimerDidFire will be omitted? – NemeSys Feb 28 '12 at 20:51
  • I cannot understand the sentence “I need to execute both queries with the first condition in databaseTimerDidFire will be omitted?” – rob mayoff Feb 28 '12 at 23:11
  • Hi, sorry for the misunderstood. My fault. I applied your solution and now the TableView still freezing and I receive 2 memory warnings from debugger before crash. See the update with the final code please. Thanks. – NemeSys Feb 29 '12 at 00:08
  • I've applied your solution but I'm receiving which I've edited in your post. I'm sorry it's too late for me now. :( – NemeSys Feb 29 '12 at 00:17
0

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.

Parag Bafna
  • 22,812
  • 8
  • 71
  • 144
  • I tried your method but the UITableView still freezes when scrolling. – NemeSys Feb 28 '12 at 20:48
  • From Apple docs: "Writing thread-creation code manually is tedious and potentially error-prone and you should avoid it whenever possible. Mac OS X and iOS provide implicit support for concurrency through other APIs. Rather than create a thread yourself, consider using asynchronous APIs, GCD, or operation objects to do the work." - https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html#//apple_ref/doc/uid/10000057i-CH6-SW17 – DougW Feb 28 '12 at 21:37