3

In my iPhone app, I am occasionally seeing a crash caused by tableView:cellForRowAtIndexPath: being called on a background thread.

Obviously, this should not be happening. I'm not calling it, my object is a delegate to a UITableView and the foundation is calling it - the only thing I see in the stack for the thread in question is

-_WebTryThreadLock(bool)
-_dequeuReusableViewOfType
-tableView:cellForRowAtIndexPath:
-_createPreparedCellForGlobalRow:withIndexPath
-_pthread_qathread

The crash occurs in WebTryThreadLock - no surprise really, it's not threadsafe and should not be called off the main thread.

But how do I figure out WHY my tableView delegate is being called on a background thread?

I wonder - if I were to call [tableView reloadData] on a background thread, would that do it?

I always thought it would just dispatch the call on the main thread regardless. I'm not sure I am even doing that, but I might be, and I will check for that, but really, shouldn't UIKit check for that and call the delegate methods on the main thread anyway?

Chait
  • 1,052
  • 2
  • 18
  • 30
Jordan
  • 117
  • 1
  • 12
  • Can you post the detailed crash log. Also, are you including any third-party frameworks (any at all)? – Robotic Cat Jul 05 '13 at 17:21
  • 1
    You ask, "shouldn't UIKit check for that?" I can understand why you ask that, but the simple fact is that it doesn't and you simply must just ensure that your tableview related calls are performed in the main queue. – Rob Jul 05 '13 at 17:33
  • post your code and crash log to get a better answer – Jatin Jul 05 '13 at 18:12

2 Answers2

3

You can't call [tableView reloadData] on a secondary thread. You can't call any UIKit stuff on a secondary thread (with a few exceptions, such as UIImage). This includes all tableView methods, including straight getters and setters. It doesn't matter whether it is rendering related or not.

Benjamin Mayo
  • 6,649
  • 2
  • 26
  • 25
  • That was my understanding as well, thank you for confirming that. After checking my code carefully, I am confident I am NOT doing that. But I will triple check to be triple sure. So, can you think of any (other) scenario which might result in the foundation calling cellForRowAtIndexPath: on a bg thread? – Jordan Jul 05 '13 at 16:45
  • 2
    @Jordan There are four different `reload...` methods for table views, so make sure you don't call any of them from a background thread. Also the `scrollTo...` methods. Also don't call anything that would change the frame of your table view or, for example, popping off a view controller you pushed onto it, etc. This is all UIKit stuff, so if you make sure you do all UIKit calls from the main queue, you will be fine. – Rob Jul 05 '13 at 17:28
  • 1
    "so if you make sure you do all UIKit calls from the main queue, you will be fine" - yes, well, that is what I thought as well. It does seem likely that I have some code somewhere which is doing this, but I have not found it yet! – Jordan Jul 05 '13 at 18:58
  • 3
    Well, in the end, this answer is technically correct - I was in fact calling setNeedsLayout - on the tableView's superview - from a background thread, through an indirect path which I did not notice at first. My bad. – Jordan Jul 05 '13 at 19:55
2

It might be interesting to sprinkle some of these around your view controller:

if (![NSThread isMainThread]) { NSLog(@"Huh?"); }

I'm pretty sure UIKit/IOS would not decide on it's own to call a table view delegate method on a background thread. Do you have any dispatch_async, detachNewThreadSelector, performSelectorInBackground?

ax123man
  • 578
  • 5
  • 17
  • > 'm pretty sure UIKit/IOS would not decide on it's own to call a >table view delegate method on a background thread - Yes, that was my belief as well, and so far, I have no reason to believe otherwise. – Jordan Jul 05 '13 at 19:01
  • I am checking isMainThread everywhere I can, and doing a stack dump in places where I find I am on the wrong thread, in the hope of learning how I got there in the first place. – Jordan Jul 05 '13 at 19:03