ARC: Dealloc not being called - ios

I don't understand why I need to have a weak self in some blocks, while others seems to work fine.
If I don't have a weak ref to self with the Notification block, the dealloc won't be released. It works perfectly fine with the second though.
//When using this, dealloc is NOT being called
[[NSNotificationCenter defaultCenter] addObserverForName:PROD_DONE object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self hideAds];
}];
//When using this, dealloc IS being called
[_match endMatchInTurnWithMatchData:_match.matchData completionHandler:^(NSError *error) {
[self hideAds];
}];
If I create a weak ref to self, it works:
__weak GameViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:PROD_DONE object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[weakSelf hideAds];
}];

This is because one reference goes away over time (for example - when the completion handler is called), the block is released. In this case, there is no retain cycle, because the reference to self will be released.
However, with the NSNotification example, the block reference must always be retained (unless it is removed manually) because it is still listening for the NSNotification. In this case, the reference to self causes a retain cycle causing the class to not be retained.

Related

Notifications causing no dealloc to be called

I am trying to use this within a project: https://github.com/zakkhoyt/VWWPermissionKit
I do not understand KVO/Notification Center as much as I'd like so posting a question.
Basically the init and dealloc for the Permission Manager look like this:
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserverForName:VWWPermissionNotificationsPromptAction object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
VWWPermission *permission = note.userInfo[VWWPermissionNotificationsPermissionKey];
[permission presentSystemPromtWithCompletionBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
[permission updatePermissionStatus];
if(permission.status == VWWPermissionStatusDenied){
[self.permissionsViewController displayDeniedAlertForPermission:permission];
}
[self checkAllPermissionsSatisfied];
});
}];
});
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
[self readPermissions];
});
}];
}
return self;
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
If I want to read a set of permissions I would call this:
NSArray *permissionsToRead = #[
[VWWCoreLocationWhenInUsePermission permissionWithLabelText:nil],
[VWWNotificationsPermission permissionWithLabelText:nil]
];
[VWWPermissionsManager readPermissions:permissionsToRead resultsBlock:^(NSArray *permissions) {
// Do something with the result
}
}];
This all works fine. The issue is that the dealloc is not being called, therefore the Notifications are still being called such as the UIApplicationDidBecomeActiveNotification being created in the init method.
As far as I can see the Permission Manager is created and not referenced and therefore it just hangs around.
The public method for the readPermssions is as follows:
+(void)readPermissions:(NSArray*)permissions resultsBlock:(VWWPermissionsManagerResultsBlock)resultsBlock{
VWWPermissionsManager *permissionsManager = [[self alloc]init];
[permissionsManager readPermissions:permissions resultsBlock:resultsBlock];
}
A new instance is created and another method is called then passes the resultsBlock back. There is nothing that releases this as far as I can tell. How would I get the dealloc to be called?
It's because NSNotificationCenter is retaining the observer object, which is retaining the block that is registered with it, which is capturing your view controller and preventing it from being deallocated.
If you want your view controller to be able to be released then you should create a weak reference (__weak typeof(self) weakSelf = self;) to it outside the block and use weakSelf inside the block.
You also aren't removing the observer correctly. When you add an observer using the notification center block api it returns an object which is what it is actually adding as the observer and which you need to keep a reference to and pass to removeObserver:.
I would suggest just not using the method to observe with a block since it adds more management trouble than it's worth. Use the one that takes a selector instead.

Can someone explain this strange behaviour in iOS (central dispatch + notifications + UI refresh)?

Brief : I have a queue that sends notifications. A view controller subscribes to them and when it receives them it displays an image.
Problem : the first time, everything goes well. When I come back later to the view, I see the log that the notification was received but the image is not displayed.
Note : the background thread is a queue : dispatch_queue_create("scan", DISPATCH_QUEUE_SCAN) and from this queue the notification are posted.
#property(strong, nonatomic) id observer;
- (void)viewDidAppear:(BOOL) animated {
[super viewDidAppear:animated];
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:MY_NOTIF object:nil queue:nil usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"notification");
[self.img setImage:[UIImage imageNamed:#"register-ok"]];
[self.img setNeedsDisplay]; // useless
});
}
}
- (void)viewDidDisappear:(BOOL) animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:observer name:MY_NOTIF object:nil];
}
This is really getting me crazy. Thanks in advance for your help
EDIT : changes done :
NSLog(#"viewDidAppear. createObserver. self: %#");
[[NSNotificationCenter defaultCenter] addObserverForName:MY_NOTIF object:nil queue:nil usingBlock:^(NSNotification *note) {
NSLog(#"will call redraww. self: %#");
[self performSelectorOnMainThread:#selector(redraww:) withObject:nil waitUntilDone:YES];
}
-(void) redraww:(NSObject*)input {
NSLog(#"redraww self : %#", self);
[self.img setImage:[UIImage imageNamed:#"register-ok"]];
}
-(void) onRegistrationFinish {
NSLog(#"remove observer. self %#", self);
[[NSNotificationCenter defaultCenter] removeObserver:self name:(NSString *)NOTIF_SOLE_UUID object:nil];
}
console log
viewDidAppear. createObserver self: <RegisterLeftViewController: 0x15e34550>
will call redraww. self:<RegisterLeftViewController: 0x15e34550>
redrawww self : <RegisterLeftViewController: 0x15e34550>
remove observer. self <RegisterLeftViewController: 0x15e34550>
************************* Second time
viewDidAppear. createObserver self: <RegisterLeftViewController: 0x15e98ba0>
will call redraww. self:<RegisterLeftViewController: 0x15e34550>
redrawww self : <RegisterLeftViewController: 0x15e34550>
remove observer. self <RegisterLeftViewController: 0x15e98ba0>
Seems like you're doing a few things that could cause trouble.
The trouble could be coming from a number of places. Here's some things that may fix the problem:
Move observer registration to viewDidLoad
Move observer de-registration to dealloc
use __block __weak id observer to ensure self is properly captured in your notification handler. See iOS NSNotificationCenter Observer not being removed
Hope that helps

Why doesn't adding observer for a notification with blocks work?

I have this method to get shift my text fields when they keyboard appears:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
- (void)keyboardWillShow:(NSNotification *)notification {
self.scrollView.contentOffset = CGPointMake(0.0f, keyboardShift);
}
Then I tried to use the block method instead:
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
self.scrollView.contentOffset = CGPointMake(0.0f, keyboardShift);
}];
However, using this method I set a breakpoint inside the block but it's not being called. Is there anything I'm missing? Why doesn't this method work but the other one does?
What if you try replacing the nil parameter for the queue with [NSOperationQueue mainQueue]
Also, if you go with the block API, make sure you keep a reference to the token returned so that you can remove the observer at the right time later on.
addObserverForName returns an object that you should hold on to. From the header:
The return value is retained by the system, and should be held onto by the caller in order to remove the observer with removeObserver: later, to stop observation.
See the example in the documentation.

Class cluster with ARC

I'm trying to create a class cluster as subclass of UIViewController to accomplish some points:
1. Different behavior of the ViewController depending on actual iOS version
2. iOS version checks don't clutter up the code
3. Caller doesn't need to care
So far I got the classes MyViewController, MyViewController_iOS7 and MyViewController_Legacy.
To create instances I call the method myViewControllerWithStuff:(StuffClass*)stuff which is implemented like:
+(id)myViewControllerWithStuff:(StuffClass*)stuff
{
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
{
return [[MyViewController_iOS7 alloc] initWithStuff:stuff];
}
else
{
return [[MyViewController_Legacy alloc] initWithStuff:stuff];
}
}
The caller uses myViewControllerWithStuff:. After that the so created view controller gets pushed onto a UINavigationController's navigation stack.
This nearly works as intended with one big downside: ARC doesn't dealloc the instance of MyViewController_xxx when it gets popped from the navigation stack. Doesn't matter which iOS version.
What am I missing?
UPDATE: -initWithStuff:
-(id)initWithStuff:(StuffClass*)stuff
{
if (self = [super init])
{
self.stuff = stuff;
}
return self;
}
This method is also implemented in MyViewController. The differences kick in later (e.g. viewDidLoad:).
First of all: Thanks for all your help, comments, answers, suggestions...
Of course there was another strong reference to the MyViewController-object. But it wasn't that obvious, because it was not a property or instance variable.
In viewDidLoad I did the following:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self saveState];
}];
This should prevent data loss in case the user sends the app to the background. Of course the block captures the needed parts of its environment. In this case it captured self. The block keeps self alive until it (the block) gets destroyed, which is the case when e.g. [[NSNotificationCenter defaultCenter] removeObserver:self]; gets called. But, bad luck, this call is placed in the dealloc method of MyViewController which won't get called as long as the block exists...
The fix is as follows:
__weak MyViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[weakSelf saveState];
}];
Now the block captures weakSelf. This way it can't keep the MyViewController-object alive and everything deallocs and works just fine.

Remove Observer when using addObserverForName:usingBlock

I have the following code that adds an observer in the loading of the view.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:#"com.app.livedata.jsonupdated"
object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
NSLog(#"JSONUPDATED");
}];
}
And this fires fine. However when the view is unloaded and I confirm the dealloc is called the Notification is still firing.
There doesn't seem to be a method for deactivating this observer?
Seems the solution is to track the object in the View and then you can reference it in the dealloc methods.
id observer = [[NSNotificationCenter defaultCenter] addObserverForName: /* ... */ ];
And then remove as following:
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;

Resources