Why is tableView:cellForRowAtIndexPath: being called on a background thread? - iphone

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?

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.

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?

Related

Understanding setNeedsDisplay/drawRect with Blocks

I'm trying to understand how things work in regards to concurrent programming and calling setNeedsDisplay. I basically have Three objects.
Main View - container with different UIView objects, the main one being a UIScrollView
Small Map View - a small UIView that draws a miniature version of one of the other UIView items on screem
Processor - a delegate of the Main View that calculates what's on screen and calls the Main View back with what's in view.
So a simple use case of what's going on is the user touches the ScrollView and then the processor updates what's in view of the scrollView (like calculating coordinates, center point, etc) It does this using blocks and does it asynchronously. This then posts a notification to the MainView object.
When the MainView receives the notification, it just calls
[smallMap setNeedsDisplay]; // example 1
I put some logs around this call, and I do see it gets called right away. However, the drawRect: of this function does not get called right away. It gets called after 2 seconds or so.
I remember reading that setNeedsDisplay just marks the view for redraw to happen on the next event of the run loop.
But if I add this code instead:
// example 2
dispatch_async(dispatch_get_main_queue(), ^{
[smallMap setNeedsDisplay];
});
My view gets redrawn right away.
I guess I'm confused as to why I have to ask for the main event loop to call setNeedsDisplay to immediately redraw something. Like in example 1, by me calling setNeedsDisplay, is that done in the background or something and that's why it doesn't get redrawn right away? I'm trying to understand the difference in what's going on behind the scenes so I know what to look for the in future. Like should I have all my calls that need to be immediately redrawn in something similar to the example 2 block? Or is it because I'm processing my data asynchronously that I need to then ask for the main queue? Thanks!
My guess is 1 of 2 things:
Your code that is running on a separate thread is calling your MainView methods from the separate thread instead of using performSelectorOnMainThread or a GCD call that invokes the code on the main thread. Thus your call to setNeedsDisplay is actually taking place on a background thread, which is a no-no, as the other poster said.
The second possibility is that your MainView code is running on the main thread, but it gets busy doing time-consuming processing, or waiting for a synchronous call to another thread to finish, and doesn't service the event loop.
You can rule out the first possibility by setting a breakpoint on your call to setNeedsDisplay and looking at the call trace in the debugger to see what thread it's running from.
Figuring out the second possibility will take a little more work. You might need to delve into instruments.
setNeedsDisplay is a UIKIT API call and has to be called from the main thread of the application, also known as the UI thread. That's why calling it in a background thread doesn't have any immediate effect and scheduling it on the main queue has immediate effects.
See this related question https://stackoverflow.com/a/6988115/172690 for a more detailed answer.

Easy and fast way to access UIView from different threads

I have an interface like this:
#interface AView : UIScrollView
{
UIView* m_view1;
UIView* m_view2;
...
}
-(void) method1;
-(void) method2;
...
#end
I need to access views from methods of the interface. I need to create, release, re-create them and also set properties.
The problem is, some methods of the interface are running in different threads. Since these methods access same views I have issues like one thread trying to re-create a view when another thread is trying to set some properties of a view being recreated.
How should I synchronize access to views?
First of all, do you know you can ONLY call the methods of the UIView class (and it's subclasses) in the main thread? But, if you are just doing create and release job in second thread, it's OK to do it.
Threading Considerations
Manipulations to your application’s user interface must occur on the
main thread. Thus, you should always call the methods of the UIView
class from code running in the main thread of your application. The
only time this may not be strictly necessary is when creating the view
object itself; but all other manipulations should occur on the main
thread.
In addition, you can use #synchronized() {object} to lock an object. But still, you can NOT call UIView's methods in second thread (in Objective-C even set property is calling method) even you've locked it.
Objective-C supports multithreading in applications. Therefore, two
threads can try to modify the same object at the same time, a
situation that can cause serious problems in a program. To protect
sections of code from being executed by more than one thread at a
time, Objective-C provides the #synchronized() directive.
The #synchronized()directive locks a section of code for use by a
single thread. Other threads are blocked until the thread exits the
protected code—that is, when execution continues past the last
statement in the #synchronized() block.
The #synchronized() directive takes as its only argument any
Objective-C object, including self.
You can use NSLock for example, but you shouldn't update your UI from other threads. The only thread from which you should update UI Is the main thread.
UIKit can only be used safely on the main thread! Do you use NSOperation?
You will see the usage ofperformSelectorOnMainThread:withObject:waitUntilDone: to move operations done from other threads to the main thread.

NSOperation finishes in the background, attempts to notify main thread, view no longer exists. Crash

I have an NSOperation running not in the main thread. It is spawned from a UITableViewController. When the operation is complete, I'd like to reload the tableview since some data has changed. I've set a delegate for the background to notify on completion. When done, I call a wrapper around reloadData specifically on the main thread using performSelectorOnMainThread.
For the most part, this works well, however, there is a non-0 chance that the original (edit)tableViewController (/edit) gets released and I get zombie calls.
So the question is in 2 parts:
Is it possible to have a delegate from the background thread without retaining the object?
Is this just a bad design? Should I be using NSNotifications instead? Would that be the preferred method of notifying in this case?
Thanks in advance.
A delegate should be retained if there is a possibility that it might be released before any operation on the delegate is invoked. You can set up a state in tableViewController to handle the case when the delegate callback is invoked and the tableViewController is not to be used (Basically make the callbacks act as no-op). Once your operation is done, just release the delegate object.
It is not a bad design but you just need to handle these conditions.

(iphone) start timer from background thread?

I create a timer with [NSTimer scheduledTimerWithTimerInterval:target:selector:...] from a background thread
It seems the timer created from a background thread doesn't call the selector given as the argument.
Is there something special I need to run a timer from background thread?
Thank you
EDIT
Here's the sequence
performSelectorInBackground: pushViewController
from the viewController's init sequence,
I alloc a timer with the above method.
and the timer selector won't get called.
You should only do things to the UI - like pushing view controllers and changing UI items - from the main thread. If you don't, things break, as you can see.
See the Cocoa Fundamentals Guide section titled "Are the Cocoa Frameworks Thread Safe?": it says "All UIKit objects should be used on the main thread only."
Original answer
There's nothing special needed. The selector will get called on the thread you did the scheduleTimer call from.

dealloc on Background Thread

Is it an error to call dealloc on a UIViewController from a background thread? It seems that UITextView (can?) eventually call _WebTryThreadLock which results in:
bool _WebTryThreadLock(bool): Tried to obtain the web lock from a thread
other than the main thread or the web thread. This may be a result of calling
to UIKit from a secondary thread.
Background: I have a subclassed NSOperation that takes a selector and a target object to notify.
-(id)initWithTarget:(id)target {
if (self = [super init]) {
_target = [target retain];
}
return self;
}
-(void)dealloc {
[_target release];
[super dealloc];
}
If the UIViewController has already been dismissed when the NSOperation gets around to running, then the call to release triggers it's dealloc on a background thread.
Yes, it is an error to make a UIViewController releasing in a background thread (or queue). In UIKit, dealloc is not thread safe. This is explicitly described in Apple's TN2109 doc:
When a secondary thread retains the target object, you have to ensure that the thread releases that reference before the main thread releases its last reference to the object. If you don't do this, the last reference to the object is released by the secondary thread, which means that the object's -dealloc method runs on that secondary thread. This is problematic if the object's -dealloc method does things that are not safe to do on a secondary thread, something that's common for UIKit objects like a view controller.
However, it's quite hard to respect this rule and if you put precondition(Thread.isMainThread) in all your view controller dealloc/deinit(), you probably would notice very weird (and hard to fix) cases in which this rule is not respected.
It seems that Apple is aware of this fragility and is moving away from this rule. In fact, when you annotate a class with #MainActor, everything is ensured to be executed in the Main Thread, except deinit(). In Swift 6, the compiler prevents you from calling main actor code from deinit().
Even if in the past we have been asked to ensure deallocation in the main thread, in the future we will be asked to ensure that deallocation can happen in any thread.
The simple rule is that it's an error to do anything on a UI* from a background thread.
The second post here had useful info, but their answer didn't work for me.
UIWebView in multithread ViewController
It also seems that this may have been addressed in iPhone OS 4, but not sure.
I ended up not releasing my UIWebView in the controller's dealloc when [NSThread isMainThread] was NO. Would rather leak than crash (until I get a better solution).
It is an error to call dealloc on anything at any time. You should only ever call release.
You should not access any UI related instances from a background thread. This includes using getter methods because they may modify things internally. However, retain and release are thread safe for any object at any time, as long as the normal rules for retain and release are followed. UI related instances include any object that is referenced by an active UIView or UIViewController.
performSelectorOnMainThread does not do anything more than retain an object until it gets to the main thread. It is safe to call on any UI related object.