Why is UIViewController deallocated on the main thread? - ios

I recently stumbled upon The Deallocation Problem in some Objective-C code. This topic was discussed before on Stack Overflow in Block_release deallocating UI objects on a background thread. I think I understand the problem and its implications, but to be sure I wanted to reproduce it in a little test project. I first created my own SOUnsafeObject (= an object which should always be deallocated on the main thread).
#interface SOUnsafeObject : NSObject
#property (strong) NSString *title;
- (void)reloadDataInBackground;
#end
#implementation SOUnsafeObject
- (void)reloadDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.title = #"Retrieved data";
});
sleep(3);
});
}
- (void)dealloc {
NSAssert([NSThread isMainThread], #"Object should always be deallocated on the main thread");
}
#end}]
Now, as expected, if I put [[[SOUnsafeObject alloc] init] reloadDataInBackground]; inside application:didFinishLaunching.. the app crashes after 3 seconds due to the failed assertion. The proposed fix seems to work. I.e. the app doesn't crash anymore if I change the implementation of reloadDataInBackground to:
__block SOUnsafeObject *safeSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
safeSelf.title = #"Retrieved data";
safeSelf = nil;
});
sleep(3);
});
Okay, so it seems like my understanding about the problem and how it can be solved under ARC is correct. But just to be 100% sure.. Let's try the same with an UIViewController (since an UIViewController will probably fill in the role of SOUnsafeObject in real life). The implementation is almost identical to that of the SOUnsafeObject:
#interface SODemoViewController : UIViewController
- (void)reloadDataInBackground;
#end
#implementation SODemoViewController
- (void)reloadDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.title = #"Retrieved data";
});
sleep(3);
});
}
- (void)dealloc {
NSAssert([NSThread isMainThread], #"UI objects should always be deallocated on the main thread");
NSLog(#"I'm deallocated!");
}
#end
Now, let's put [[SODemoViewController alloc] init] reloadDataInBackground]; inside application:didFinishLaunching... Hmm, the assertion doesn't fail.. The message I'm deallocated! is printed to the console after 3 seconds so I'm pretty sure the view controller is getting deallocated.
Why is the view controller deallocated on the main thread while the unsafe object is deallocated on a background thread? The code is nearly identical. Does UIKit do some fancy stuff behind the scenes to make sure an UIViewController is always deallocated on the main thread? I'm starting to suspect this since the following snippet also doesn't break my assertion:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
SODemoViewController()
});
If so, is this behavior documented somewhere? Can this behavior be relied upon? Or am I just totally wrong and is there something obvious I'm missing here?
Notes: I'm fully aware of the fact that I can use a __weak reference here, but let's assume the view controller should still be alive to execute our completion code on the main thread. Also, I'm trying to understand the core of the problem here before I circumvent it. I converted the code to Swift and got the same results as in Objective-C (the fix for SOUnsafeObject is there syntactically even uglier).

tl;dr - While I can find no official documentation, the current implementation does indeed ensure that dealloc for UIViewController happens on the main thread.
I guess I could just give a simple answer, but maybe I can do a little "teach a man to fish" today.
OK. I can't find documentation for this anywhere, and I don't remember it ever being said publicly either. In fact, I have always gone out of my way to make sure view controllers were deallocated on the main thread, and this is the first time I've ever seen someone indicate that UIViewController objects get automatically deallocated on the main thread.
Maybe someone else can find an official statement, but I couldn't find one.
However, I do have some evidence to prove that it does indeed happen. Actually, at first, I thought you were not properly handling your blocks or reference counts, and somehow a reference was being retained on the main thread.
However, after a cursory look, I was interested enough to try it for myself. To satisfy my curiosity, I made a class similar to yours that inherited from UIViewController. Its dealloc ran on the main thread.
So, I just changed the base class to UIResponder, which is the base class of UIViewController, and ran it again. This time its dealloc ran on the background thread.
Hmmm. Maybe there is something going on behind closed doors. We have lots of debugging tricks. The answer always lies with the last one you try, but I figured I'd try my usual bag of tricks for this kind of stuff.
Log Notifications
One of my favorite tools to find out how things are implemented is to log all notifications.
[[NSNotificationCenter defaultCenter]
addObserverForName:nil
object:nil
queue:nil
usingBlock:^(NSNotification *note) { NSLog(#"%#", note); }];
I then ran using both classes, and didn't see anything unexpected or different between the two. I didn't expect to, but that little trick is very simple, and it has helped me tremendously in discovering how a lot of other things worked, so it's usually first.
Log Method/Message Sends
My second trick it to enable method logging. However, I don't want to log all methods, just what happens between the time the last block executes, and the call to dealloc. So, turned on method logging by adding this as the last line of the "sleeping" block.
instrumentObjcMessageSends(YES);
And I turned logging back off, with this as the first line of the dealloc method.
instrumentObjcMessageSends(NO);
Now, this C function can't be readily found in any headers that I know of, so you need to declare it at the top of your file.
extern void instrumentObjcMessageSends(BOOL);
The logs go into a unique file in /tmp, named msgSends-.
The files for the two runs contained the following output.
$ cat msgSends-72013
- __NSMallocBlock__ __NSMallocBlock release
- SOUnsafeObject SOUnsafeObject dealloc
$ cat msgSends-72057
- __NSMallocBlock__ __NSMallocBlock release
- SOUnsafeObject UIViewController release
- SOUnsafeObject SOUnsafeObject dealloc
There is not too much surprising about that. However, the presence of UIViewController release indicates that UIViewController has a special override implementation for the +release method. I wonder why? Could it be to specifically transfer the call to dealloc to the main thread?
Debugger
Yes, this is the first thing I thought of, but I had no evidence that there was an override in UIViewController so I went through my normal process. I have found when I skip steps, it typically takes longer.
Anyway, now that we know what we are looking for, I put a breakpoint on the last line of the "sleeping" block and made the class inherit from UIViewController.
When I hit the breakpoint, I added a manual breakpoint...
(lldb) b [UIViewController release]
Breakpoint 3: where = UIKit`-[UIViewController release], address = 0x000000010e814d1a
After continuing, I was greeted with this awesome assembly, which confirms visually what is happening.
pthread_main_np is a function that tells you if you are running on the main thread. Stepping through the assembly instructions confirmed that we are not running on the main thread.
Stepping further, we get to line 27, where we jump over the call to dealloc, and instead run what you can easily see is code to run a dealloc-helper on the main thread.
Can You Count on This Going Forward?
Since I can't find it documented, I don't know that I would count on this happening all the time, but it is very convenient, and obviously something they intentionally put into the code.
I have a set of tests that I run every time Apple releases a new version of iOS and OSX. I assume most developers do something very similar. I think what I would do is write a unit test, and add it to that set. Thus, if they ever change it back, I'll know as soon as it comes out.
Otherwise, I tend to think this may be one of those things that can safely be assumed.
However, be aware that subclasses may choose to override release (if they are compiled with ARC disabled), and if they do not call the base class implementation, you will not get this behavior.
Thus, you may want to write tests for any third-party view controller classes you use.
My Details
I only tested this with XCode 6.4, Deployment target 8.4, simulating iPhone 6. I'll leave testing with other versions as an exercise for the reader.
BTW, if you don't mind, what are the details for your posted example?

Related

Memory Management in a Block Using Notifications

According to the Xcode instruments, my code has a memory leak (at #3). But I get the feeling I'm missing something in my mental model of what's going on, so I have a few questions about the following logic:
__block MyType *blockObject = object; //1
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.selectedObjects containsObject:blockObject]) { //2
[self.selectedObjects removeObject:blockObject];
[[NSNotificationCenter defaultCenter] postNotificationName:ObjectDeselectionNotification object:blockObject]; //3
} else {
[self.selectedObjects addObject:blockCart];
[[NSNotificationCenter defaultCenter] postNotificationName:ObjectSelectionNotification object:blockCart];
}
});
1) I'm using a __block reference because I'm executing this code async and don't want a reference to this variable copied to the heap. Is this a valid usage of __block even though I'm not mutating the variable?
2) Calling self.selectedObjects will create a retain on self. Does the block automatically release this after it has exited?
3) I apparently have a leak at this point, but I'm not exactly sure why. Is NotificationCenter retaining my __block object that is supposed to be disposed of after my block exits?
From the code you've shown, I don't see any problems...
1) Your object would not be "copied" onto the heap - it is already on the heap being an alloc'd object. Rather, it's reference count would be incremented by 1 as it is now owned by the block. You do not need the __block reference as you are not assigning anything to the pointer. In fact, you do not need blockObject at all and can just pass object.
2.) self should be released once the block is done. However, post a notification is synchronous (this block will not finish until all the objects responding to the notification are done).
3.) I'm not sure what the exact implementation that NSNotificationCenter uses, but it doesn't really matter because the posting is synchronous. It will call every observer of your notification and the selectors they want to receive your notification on
It seems as though you are running all this code within another block - can you paste the full method?
Please remove this answer if incorrect (you've already accepted) but I'm not sure you accepted because the answer worked for you.
I don't think you should be referencing self in that block - you will be creating a retain cycle.
__weak YourClass *weakSelf = self;
use weakSelf instead and cut the tie between the creator and the block floating on the dispatch queue?

Any way to guarantee a code will always be executed on the main thread?

Excuse me if this question sounds stupid but this is beyond my knowledge of Objective-C.
I am developing some classes that have to always be executed on the main thread.
Ok, I can pollute my code with a bunch of
dispatch_async(dispatch_get_main_queue(),
^{
});
but I would like to know if there is something I can do to prevent the methods of this class from running on other threads that is not the main or to at least warn during debugging, compiling, or whatever if they are used not on the main thread.
thanks
I sprinkle such methods with my BLOCK_UI() macro from https://github.com/gradha/ELHASO-iOS-snippets. At runtime the macro will assert if the method is not running on the main thread. The macro goes away in release builds because I consider calling such an API in the background a programmer error, but if you want to make an API which is permissive with the programmer, you can also check for the main thread and invoke yourself in the main thread if needed. Example:
if ([NSThread isMainThread]) {
[self do_request:url];
} else {
[self performSelectorOnMainThread:#selector(do_request:)
withObject:url waitUntilDone:NO];
}
You can always check via the helpful API "[NSThread isMainThread]"

iOS and Objective-C: most of CPU time is spent in [NSObject release] and [NSObject retain] but class method is not doing any memory operations

An image processing applications runs fast on the simulator, but is really slow on a real device (iPhone 4GS).
When running the application under "instruments", I see the following call tree:
Note that the calls within the red circle are reported to take almost all of the CPU time of the method.
The method in question is a class method (not an instance method), with the following code:
#implementation Line2F
+ (CGFloat)signTested:(Point2F *)tested p1:(Point2F *)p1 p2:(Point2F *)p2
{
return [Line2F signTestedX:tested.x testedY:tested.y
p1x:p1.x p1y:p1.y
p2x:p2.x p2y:p2.y];
}
+ (CGFloat)signTestedX:(CGFloat)testedX testedY:(CGFloat)testedY
p1x:(CGFloat)p1x p1y:(CGFloat)p1y
p2x:(CGFloat)p2x p2y:(CGFloat)p2y
{
return (testedX - p2x) * (p1y - p2y) - (p1x - p2x) * (testedY - p2y);
}
#end
Can anyone explain why is most of the CPU time is spent on [NSObject release] and [NSObject retain]?
If it doesn't know any better ARC will retain all the arguments to a method and release them when the method exits (see this objc-language mailing list email).
You should be able to avoid this by annotating the arguments to +signTested:p1:p2: with either __weak or __unsafe_unretained, per your needs.
Well could be a lot of stuff. As FrozenDevil says it could be related to ARC if you are using it. I imagine that most probably the method is called different times inside a huge loop. Try to pass weak references, but of course you must be sure that they exist for the whole process. I would try also to optimize the loop embedding each cycle in an autorelease pool.

I can't make a new instance of NSManagedObject on background thread with non-main MOC

I have researched a ton of posts regarding Core Data on background threads, and I feel like I understand (on paper) what needs to be going on. I guess we'll see. I am working on migrating an existing OS X app to Core Data, and am having issues making new instances of my NSManagedObject on an async thread.
Here is a sample of the code I am running right after I have moved onto a background thread:
NSLog(#"JSON 1");
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
asset = (MTAssetInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"Info" inManagedObjectContext:context];
NSLog(#"JSON 2");
The result is that the first log message (#"JSON 1") gets called 31 times, and the second one (#"JSON 2") is never called. The object isn't being made and returned correctly.
The model for this Info entity is quite complex with a few transformable attributes that may or may not be setup correctly. The weird thing is that similar code run on the main thread and the main MOC works great. No issues.
EDIT - Some more context
The async call originates from here:
for (NSNumber *sectionID in sectionsToShow) {
dispatch_group_async(group, queue, ^{
MTAssetInfo *asset = [self assetWithRefID:[sectionID unsignedIntegerValue]];
if (asset != nil) {
[sectionsLock lock];
[sectionsTemp addObject:asset];
[sectionsLock unlock];
}
});
}
The assetWithRefID method never returns with an object because of the other code snippet. It never successfully pulls an NSManagedObject out of the context on the background thread.
You are going to have to provide more information to get real help, but I bet your problem is an error happening in the NSManagedDocument background thread.
I'd register a NSNotificationCenter for ALL messages (name:nil object:nil) and just print them out. I bet you see a status change or error message in there that is failing.
You might want to try a #try/#catch block around it just to see if exceptions are being thrown.
Maybe it will give you more to go on.
One other suggestion... Swizzling isn't necessarily the right tool for production stuff, but it's almost unbeatable for debugging. I have method-swizled several entire classes, so that it sends a detailed NSNotification before/after each invocation.
It has saved me tons of time, and helped me track down some wicked bugs. Now, when something is going on in CoreData, I take out my set of classes, link them in, and see all the detail I want.
I know that does not exactly answer you question, but hopefully it will put you on the track so you can provide some more information and get it all fixed.
If that's too much for you, create a subclass and instantiate that, with a similar method for calling super. You can get a real idea of the entire flow pretty easily.

How to open/create UIManagedDocument synchronously?

As mentioned in title, I would like to open UIManagedDocument synchronously, i.e, I would like my execution to wait till open completes. I'm opening document on mainThread only.
Current API to open uses block
[UIManagedDocument openWithCompletionHandler:(void (^)(BOOL success))];
Locks usage mentioned at link works well on threads other than main thread. If I use locks on mainThread, it freezes execution of app.
Any advice would be helpful. Thanks.
First, let me say that I strongly discourage doing this. Your main thread just waits, and does nothing while waiting for the call to complete. Under certain circumstances, the system will kill your app if it does not respond on the main thread. This is highly unusual.
I guess you should be the one to decide when/how you should use various programming tools.
This one does exactly what you want... block the main thread until the completion handler runs. Again, I do not recommend doing this, but hey, it's a tool, and I'll take the NRA stance: guns don't kill people...
__block BOOL waitingOnCompletionHandler = YES;
[object doSomethingWithCompletionHandler:^{
// Do your work in the completion handler block and when done...
waitingOnCompletionHandler = NO;
}];
while (waitingOnCompletionHandler) {
usleep(USEC_PER_SEC/10);
}
Another option is to execute the run loop. However, this isn't really synchronous, because the run loop will actually process other events. I've used this technique in some unit tests. It is similar to the above, but still allows other stuff to happen on the main thread (for example, the completion handler may invoke an operation on the main queue, which may not get executed in the previous method).
__block BOOL waitingOnCompletionHandler = YES;
[object doSomethingWithCompletionHandler:^{
// Do your work in the completion handler block and when done...
waitingOnCompletionHandler = NO;
}];
while (waitingOnCompletionHandler) {
NSDate *futureTime = [NSDate dateWithTimeIntervalSinceNow:0.1];
[[NSRunLoop currentRunLoop] runUntilDate:futureTime];
}
There are other methods as well, but these are simple, easy to understand, and stick out like a sore thumb so it's easy to know you are doing something unorthodox.
I should also note that I've never encountered a good reason to do this in anything other than tests. You can deadlock your code, and not returning from the main run loop is a slippery slope (even if you are manually executing it yourself - note that what called you is still waiting and running the loop again could re-enter that code, or cause some other issue).
Asynchronous APIs are GREAT. The condition variable approach or using barriers for concurrent queues are reasonable ways to synchronize when using other threads. Synchronizing the main thread is the opposite of what you should be doing.
Good luck... and make sure you register your guns, and always carry your concealed weapons permit. This is certainly the wild west. There's always a John Wesley Harden out there looking for a gun fight.

Resources