NSOperation and UIKit problem - objective-c

I am doing my download with an object which was inherited from NSOperation. I have read the documentation and when my operation finished I must call the
[self.delegate performSelectorOnMainThread:#selector(operationDidFinish:) withObject:self waitUntilDone:YES];
method. It needs to be called on the main thread, because the UIKit is not thread safe and the documentation says this in these non thread safe frameworks cases.
In the delegate method I am drawing a pdf or an image, but because it is drawn on the main thread the User Interface is very laggy until the drawing is finished.
Maybe can you suggest me a good way to avoid this problem?

I am assuming you are downloading image data, decoding it, and rendering the image. The last bit, render, has to happen on the main thread. Can you move the decode part to your download thread? For example, use CGImage calls to decode a png or jpeg, so you have a CGImageRef ready to use before calling operationDidFinish. If you can work with the PDF as images, it would be better to convert it than decode it in the main thread.

Related

Able to use UIKit class in a background thread

I experimented and was able to instantiate UIKit class (UIImageView in this case) in a background thread without issue. I have seen app crashes by doing things with UIKit on a background thread. Behind the scene, what causes exception when dealing with UIKit on a non-main thread? For example, if I stay completely away from dealing with the view hierarchy, is it safe?
It is not safe. There are some documented tasks that are safe to call on a background task, e.g. drawing a string into an image, accesing UIFont, pushing/popping/using current graphics context. However, mostly the behavior is undefined or documented as unsafe. You never know what shared resources the classes are using in the background (or they will in future releases). They could use shared memory pools, for example.
From my experience, in most cases you won't see a problem if the UIView is not added to any UIWindow but still it is not safe.
For example, I think that loading things from nibs (using UINib) must be done only from the main thread... and any view can load subviews from nibs.

Multiple threads to draw in NSView

In my code, I subclassed NSView and in its drawRect method, I am spawning three threads to perform the drawing.
-(void)drawRect:(NSRect)dirtyRect
{
[[self window] setAllowsConcurrentViewDrawing:YES];
[self setCanDrawConcurrently:YES];
[NSThread detachNewThreadSelector:#selector(DrawText) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawRectangle) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawGradient) toTarget:self withObject:nil];
//Wherease these functions DrawText, DrawRectangle and DrawGradient performs their task as suggested by name.
//In DrawText, DrawRectangle, and DrawGradient lockFocus and unlockFocus is being
// used for multithreaded drawing.
}
When I run the same program from Xcode, it is running fine. Output is shown below.
But when I run it from the outside, there is problem and output is shown below.
First, I would like to know is it right way to draw from a secondary thread? Or what is another way to draw from a secondary thread?
What is the reason behind this problem?
Ken Aspeslagh is somewhat incorrect about drawing from secondary threads (he is correct it is generally a bad idea). From what I can see of your code you don't have a good use case for drawing on a secondary thread. Can you explain why you want to do this?
you yourself have already discovered setCanDrawConcurrently: which explicitly talks of invoking drawRect: from a background thread. Note that the views window must have allowsConcurrentViewDrawing set to YES for this to work (it is the default).
Apples own Cocoa Drawing Guide has a section on drawing from secondary threads. I have highlighted some portions I think are relevant to you.
The Application Kit maintains a unique graphics context for each window and thread combination. Because each thread has its own graphics context object for a given window, it is possible to use secondary threads to draw to that window. There are some caveats, however.
During the normal update cycle for windows, all drawing requests are sent to your application’s main thread for processing. The normal update cycle happens when a user event triggers a change in your user interface. In this situation, you would call the setNeedsDisplay: or setNeedsDisplayInRect: method (or the display family of methods) from your application’s main thread to invalidate the portions of your view that require redrawing. You should not call these methods from any secondary threads.
If you want to update a window or view from a secondary thread, you must manually lock focus on the window or view and initiate drawing yourself. Locking focus configures the drawing environment for that window's graphics context. Once locked, you can configure the drawing environment, issue your drawing commands as usual, and then flush the contents of the graphics context to the window buffer.
In order to draw regularly on a secondary thread, you must notify the thread yourself. The simplest way to send regular notifications is using an NSTimer or NSAnimation object. For more information on how to animate content, see “Advanced Drawing Techniques.”
The Cocoa Threading Programming Guide also says this:
If you want to use a thread to draw to a view, bracket all drawing code between the lockFocusIfCanDraw and unlockFocus methods of NSView
Just an aside, GCD block invocation is probably a much nicer method for performing small sets of operations in the background than NSThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// you can put each of these calls in their own queue if you want
[self DrawText];
[self DrawRectangle];
[self DrawGradient];
});
However, that likely has nothing to do with your problem; I mention it only because I think it will serve you better to use GCD queues.
You should only be drawing to the screen from the main thread.
Edit: It is apparently really complicated so you're better off drawing to the screen from the main thread. ;)
If you need to render something that takes too much time to avoid blocking the main thread, consider using a thread to do the drawing to an offscreen context, and then copy that context to the screen on the main thread.
I read about NSGraphicsContext Restriction at Thread guide.
Here, I found the following line:
If you do any drawing from a secondary thread, you must flush your drawing calls manually. Cocoa does not automatically update views with content drawn from secondary threads, so you need to call the flushGraphics method of NSGraphicsContext when you finish your drawing. If your application draws content from the main thread only, you do not need to flush your drawing calls.
After calling flushGraphics, it works fine.

loading a nib on a background thread

I just spent a day tracking down a really weird bug. It was a UILabel being overreleased, although in the code there was no over-releasing. If I commented out the release of the label, code was fine.
I tracked it down to a UIView that was being released in a background thread. Which didn't seem right as you're supposed to access all UI elements from the main thread. I then found a nib file that was being loaded in a background thread using:
[[NSBundle mainBundle] loadNibNamed:#"nib name" owner:self options:nil];
I don't need to go into the details of what the code was doing, but my question is:
If loadNibNamed is called in a background thread and the view loaded is cached by the nib loading process, then the main thread wants to load that nib too - but loads it from a cache. When my object is released - is the view loaded from the nib in the background also released in the background..?? Doesn't really make sense as the background thread would no longer exist anyway....
I don't know, but I resolved it by wrapping the loadNibNamed part of the code in a GCD dispatch onto main thread synchronously.
From Mike Ash's article
Dangerous Cocoa Calls
NSBundle
This one has half of the problems of NSHost. NSBundle returns shared objects, but is not thread safe, so it's main-thread only. It's still safe to use from the main thread. The reason I mark it as dangerous is because the fact that it's unsafe to use from secondary threads is not really documented, but rather has to be inferred from the fact that it's not thread safe and the fact that the instances are shared, and it can be tempting to use it from other threads.
All UI-related operations should be done from the main thread.

How can I update the contents of a CALayer while the main thread is blocked?

An instance of AVCaptureVideoPreviewLayer continues to update its contents from the video capture stream even while the main thread is blocked. Is it possible to generally replicate this behaviour with a custom subclass of CALayer? In other words, given raw image data, can we update what is displayed on-screen while the main thread is blocked?
You can't update anything in the view when the main thread is blocked. The whole of UIKit is single-threaded and runs on the main event loop. Video capture is a special case because it draws directly to the screen buffer, but you won't be able to replicate it yourself.
Furthermore, if you do a long-running task on the main thread, iOS will assume your app has crashed and kill it after a few seconds anyway.
Why not perform your other task on a background thread instead? That's the standard practice.
I've found a way to update the UI on non-UI-Thread.
We can excute the code in any thread, and it actually changes the layer's transform even when the main thread is sleeping.
self.labelLayer.transform = CATransform3DMakeScale(1.2, 1.2, 1.0);
So if anyone can explain this, please feel free to concat me!

iOS XML parsing on main or background thread

When parsing XML in Objective-C on an iOS app, when can the main thread be used and when should the parsing happen on a background thread? Can the main thread handle SAX parsing on small documents, or should all XML parsing happen in the background?
I normally do all of my data processing on a background thread. This ensures that the UI thread isn't blocked at any point in time by whatever I'm doing.
Anything that does not call into UIKit (UIView & it's subclasses) or even suggests that it might render to the screen is completely safe to do off the main thread.
I've got several apps that process XML on a background thread. I would suggest using a NSOperation that you pass the entire XML document to, allow it to process it completely or provide a series of delegate methods that notify the main thread about it's progress. If you plan on using core data, might i suggest my own NSOperation abstract class for doing background imports.
In fact you can do some rendering on a background thread, but you must pick your API's very carefully.