In the Tasks sample app for the Dropbox Datastore API (found here) the observers are set up and discarded in viewWillAppear and viewWillDisappear, respectively:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
__weak TasksController *slf = self;
[self.accountManager addObserver:self block:^(DBAccount *account) {
[slf setupTasks];
}];
[self setupTasks];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.accountManager removeObserver:self];
if (_store) {
[_store removeObserver:self];
}
}
I am trying to set up global observers in a singleton class and am using init and dealloc since my class isn't a view controller.
//Custom init method to start up our Dropbox observer
- (id)init {
self = [super init];
if(!self) return nil;
[self startDatastore];
__weak typeof(self) weakSelf = self;
[self.store addObserver:self block:^{
[weakSelf getRemoteDropboxChanges];
}];
return self;
}
-(void)dealloc
{
//Stop listening for Dropbox changes
if(self.store) {
[self.store removeObserver:self];
}
}
...but alas, this doesn't work (the observer's block never fires). Can someone help me understand the difference?
Related
I am pretty sure this question has been asked and answered, but I am not sure what I am doing wrong that I am not being able to get the method called from NSNotificationCenter. I have two classes :
Sign In Class And Calendar View Class
I want to pass single user credential from SignIn to Calendar. I have put the Calendar in SWRevealController. NSNotification is being called but the trigger method is not called for some reason.
Sign In
- (IBAction)signIn:(id)sender {
_passWord = _password.text;
NSLog(#"%# %#",_userName,_passWord);
[[NSNotificationCenter defaultCenter] postNotificationName: #"Password" object: _passWord];
[self performSegueWithIdentifier:#"signIn" sender:self];
}
Calendar
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *ppnImage = [UIImage imageNamed:#"PPNImage.png"];
NSLog(#"came here");
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(triggerAction:) name:#"Password" object:nil];
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController )
{
[self.sidebarButton setTarget: self.revealViewController];
[self.sidebarButton setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
events = [[NSMutableArray alloc]init];
[_indicator startAnimating];
self.tableView.delegate = self;
//Creating a background queue
web = [WebRequests sharedInstance];
web.delegate = self;
[web loginView];
}
-(void) triggerAction: (NSNotification *) notification {
NSLog(#"Does not call this");
}
Here
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(triggerAction:) name:#"Password" object:nil];
is called
BUT the below method is not called
-(void) triggerAction: (NSNotification *) notification
{
NSLog(#"Does not call this");
}
Really appreciate if somebody can suggest me what am I doing wrong
upon dismissing my VC, I noticed I am not releasing everything from memory. I'm very lost as to how I would go about finding my retain cycle. I am using an NSTimer and NSNotificationCenter, but I make sure to invalidate and removeObservers before exiting, and I made sure of using a weak delegate.
Where else could my retain cycles be occurring? In animation blocks? Like this?
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.setListTableViewVertConst.constant = 0;
self.setListTableViewHeightConst.constant = 264;
} completion:^(BOOL finished) {
}];
When using GCD I make sure to use weakSelf.
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.remotePasswordTextField resignFirstResponder];
});
Thanks for any help.
EDIT:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//Send the room code to be displayed on the respective view controllers.
if ([segue.identifier isEqualToString:#"toSetListRoomVC"]) {
SetListRoomViewController *setListVC = segue.destinationViewController;
setListVC.roomCode = self.roomCodeTextField.text;
}
}
viewWIllApear
[super viewWillAppear:YES];
self.socket = [[SocketKeeperSingleton sharedInstance]socket];
self.socketID = [[SocketKeeperSingleton sharedInstance]socketID];
NSString *roomCodeAsHost = [[SocketKeeperSingleton sharedInstance]hostRoomCode];
/////////HOST/////////
if ([[SocketKeeperSingleton sharedInstance]isHost]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveHostSongAddedNotification:)
name:kQueueAdd
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveUserJoinedNotification:)
name:kUserJoined
object:nil];
NSLog(#"User is the host of this room");
self.isHost = YES;
[self viewForNoCurrentArtistAsHost];
self.roomCodeLabel.text = roomCodeAsHost;
if (!self.hostQueue) {
self.hostQueue = [[NSMutableArray alloc]init];
}
if (!self.hostCurrentArtist) {
self.hostCurrentArtist = [[NSMutableDictionary alloc]init];
}
if (!self.player) {
self.player = [[AVPlayer alloc]init];
}
if (!self.timer) {
self.timer = [[NSTimer alloc]init];
}
}
///////NOT HOST///////
else {
// Add a notifcation observer and postNotification name for updating the tracks.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveQueueUpdatedNotification:)
name:kQueueUpdated
object:nil];
//Add a notifcation observer and postNotification name for updating current artist.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveCurrentArtistUpdateNotification:)
name:kCurrentArtistUpdate
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveOnDisconnectNotification:)
name:kOnDisconnect
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveHostDisconnectNotification:)
name:kHostDisconnect
object:nil];
//Add some animations upon load up. Purple glow and tableview animation.
double delay = .4;
[self purpleGlowAnimationFromBottomWithDelay:&delay];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom];
//Set Current artist, if there is one.
NSDictionary *currentArtist = [[SocketKeeperSingleton sharedInstance]currentArtist];
[self setCurrentArtistFromCurrentArtist:currentArtist];
//Set the current tracks, if there is one.
NSArray *setListTracks = [[SocketKeeperSingleton sharedInstance]setListTracks];
if (setListTracks) {
self.tracks = setListTracks;
}
}
}
tableVIEWs
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView.tag == 1) {
if (self.isHost) {
return [self.hostQueue count];
}
else return [self.tracks count];
}
else return [self.searchTracks count];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
I noticed you are using the word "popOut" in some comments, so I'm going to assume that you are using a navigation controller.
If that is the case, your view controller is retained by the nav controller it's been embedded in and is not released. The nav controller needs to hold a reference to your VC (see UINavigationController.viewControllers)so that it can get back to it when you pop out the top/next VC in the hierarchy.
This is expected behavior.
In this code sample you are not using __weak for self
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.setListTableViewVertConst.constant = 0;
self.setListTableViewHeightConst.constant = 264;
} completion:^(BOOL finished) {
}];
However, at a glance this does not seem to be creating a retain cycle. The code you posted doesn't seem to have any, so it's probably somewhere else. Are you sure you have retain cycles?
Anyway, try instrumenting you app. It might help.
Note: I am posting this as a reference for other developers that might run into the same issue.
Why do I have a memory leak with this code:
#interface SPWKThing : NSObject
#property (strong, nonatomic) NSArray *things;
#end
#implementation SPWKThing {
BOOL _isKVORegistered;
}
- (id)init
{
self = [super init];
if (self) {
NSLog(#"initing SPWKThing");
[self registerKVO];
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key {
if ([key isEqualToString:#"things"]) {
NSLog(#"didChangeValueForKey: things have changed!");
}
}
#pragma mark - KVO
- (void)registerKVO
{
if (!_isKVORegistered) {
NSLog(#"Registering KVO, and things is %#", _things);
[self addObserver:self forKeyPath:#"things" options:0 context:NULL];
_isKVORegistered = YES;
}
}
- (void)unregisterKVO
{
if (_isKVORegistered) {
NSLog(#"Unregistering KVO");
[self removeObserver:self forKeyPath:#"things"];
_isKVORegistered = NO;
}
}
- (void)dealloc
{
NSLog(#"SPWKThing dealloc");
[self unregisterKVO];
}
#end
#implementation SPWKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self runDemo];
}
- (void)runDemo
{
SPWKThing *thing = [[SPWKThing alloc] init];
thing.things = #[#"one", #"two", #"three"];
thing = nil;
}
#end
My output is:
initing SPWKThing
Registering KVO, and things is (null)
didChangeValueForKey: things have changed!
dealloc is never called? Why? I am setting thing = nil in the last line of runDemo!
See a demo project here: https://github.com/jfahrenkrug/KVOMemoryLeak
The answer is:
Never override didChangeValueForKey: (at least not without calling super). The documentation does not warn you about this.
Use the correct method observeValueForKeyPath:ofObject:change:context: instead.
This project clearly demonstrates this: https://github.com/jfahrenkrug/KVOMemoryLeak
Basically, I have a MainViewController that presents a MPMoviePlayerViewControllerExtended and I want to dismiss MPMoviePlayerViewControllerExtended when user taps the Home Button or Power Button.
I tried this in MPMoviePlayerControllerExtended.m:
-(void)viewWillDisappear {
[self dismissMoviePlayerViewControllerAnimated];
}
-(void)viewDidDisappear {
[self dismissMoviePlayerViewControllerAnimated];
}
But it doesn't work, apparently they aren't called when Home or Power button is pressed.
Any help is appreciated.
try adding :(BOOL)animated
-(void)viewWillDisappear:(BOOL)animated {
[self dismissMoviePlayerViewControllerAnimated];
}
-(void)viewDidDisappear:(BOOL)animated {
[self dismissMoviePlayerViewControllerAnimated];
}
Also probably want to include the
[super viewWillDisappear:animated]
and
[super viewDidDisappear:animated]
in those somewhere too
-(void)viewWillDisappear:(BOOL)animated {
[self dismissMoviePlayerViewControllerAnimated];
[super viewWillDisappear:animated]
}
-(void)viewDidDisappear:(BOOL)animated {
[self dismissMoviePlayerViewControllerAnimated];
[super viewDidDisappear:animated]
}
Since both methods aren't called, I had to use Notifications.
In MPMoviePlayerViewControllerExtended.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dismissModalViewControllerAnimated:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
in my iOS application, I need to use Reachability to complete the following tasks:
When a view controller is rendered, the application needs to check the connection status, and display the connection icon image accordingly (online or offline).
When the user stays on the view controller, and if the internet connection status changes, the application will be notified with the change, and then do some tasks accordingly.
I used the Reachability class which is produced by Tony Million in my application, but a strange thing happened:
After calling the startNotified method, the reachability status changes to 0(NotReachable).
The following is my code:
BTLandingViewController.h
#import <UIKit/UIKit.h>
#import "MBProgressHUD.h"
#import "Reachability.h"
#interface BTLandingViewController : UIViewController <UIAlertViewDelegate>
{
__weak IBOutlet UIImageView *internetIndicator;
MBProgressHUD *hud;
Reachability *reach;
UIAlertView *emptyRecordsAlert;
UIAlertView *syncReminderAlert;
}
#end
Part of the code in
BTLandingViewController.m
#import "BTLandingViewController.h"
#interface BTLandingViewController ()
#end
#implementation BTLandingViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialize reachability
reach = [Reachability reachabilityWithHostname:BTReachTestURL];
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Register with reachability changed notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
NetworkStatus internetStatus = [reach currentReachabilityStatus]; // value = 1 or 2
***// PS: If there is connection, before [reach startNotifier], the internetStatus's value reflects the connection status (1 or 2)***
// Start notify reachability change
[reach startNotifier];
internetStatus = [reach currentReachabilityStatus]; // Value = 0;
***// PS: Even if there is connection, after [reach startNotifier], the internetStatus's value becomes 0***
}
Thank you for your help.
I have finally solved this problem by working around the startNotifier trap I mentioned above. Calling startNotifier does change the network status to 0, but I declare a variable to store the initial network status when the view controller is rendered. And then use the value of the variable in a if statement in the method which handles the action triggered by reachabilityChanged notification. The following is my code:
BTLandingViewController.h
#import <UIKit/UIKit.h>
#import "MBProgressHUD.h"
#import "Reachability.h"
#interface BTLandingViewController : UIViewController <UIAlertViewDelegate>
{
__weak IBOutlet UIImageView *internetIndicator;
MBProgressHUD *hud;
NSInteger initialNetworkStatus;
UIAlertView *emptyRecordsAlert;
UIAlertView *syncReminderAlert;
}
#end
BTLandingViewController.m
#import "BTLandingViewController.h"
#import "BTSyncEngine.h"
#import "BTInstructorLoginViewController.h"
#import "BTGlobalParams.h"
#interface BTLandingViewController ()
#end
#implementation BTLandingViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewDidUnload {
internetIndicator = nil;
[super viewDidUnload];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Register with sync complete notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(syncCompleted:) name:#"BTSyncEngineSyncCompleted" object:nil];
// Register with reachability changed notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
Reachability *reach = [Reachability reachabilityWithHostname:BTReachTestURL];
reach.reachableBlock = ^(Reachability * reachability){
dispatch_async(dispatch_get_main_queue(), ^{
initialNetworkStatus = 1;
[internetIndicator setImage:[UIImage imageNamed:#"online_indicator.png"]];
// Start syncing local records with the remote server
[[BTSyncEngine sharedEngine] startSync];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Sync in progress";
});
};
reach.unreachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
initialNetworkStatus = 0;
[internetIndicator setImage:[UIImage imageNamed:#"offline_indicator.png"]];
// If the initial sync has been executed
// then use the cached records on the device
if ([[BTSyncEngine sharedEngine] initialSyncComplete]) {
// Go to instructor login screen
BTInstructorLoginViewController *instructorLoginViewController = [[BTInstructorLoginViewController alloc] init];
[instructorLoginViewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:instructorLoginViewController animated:YES completion:nil];
}
// If the initial sync has not been executed
// show an alert view
else {
emptyRecordsAlert = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"Please connect to the internet to load data for the first time use." delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[emptyRecordsAlert show];
}
});
};
[reach startNotifier];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// Unregister with reachability changed notification
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
// Unregister with sync complete notification
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"BTSyncEngineSyncCompleted" object:nil];
}
- (void)syncCompleted:(NSNotification *)note
{
// Update the global sync completed flag
[[BTGlobalParams sharedParams] setSyncCompleted:YES];
// Hide syncing indicator
[MBProgressHUD hideHUDForView:self.view animated:YES];
// Go to instructor login screen
BTInstructorLoginViewController *instructorLoginViewController = [[BTInstructorLoginViewController alloc] init];
[instructorLoginViewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:instructorLoginViewController animated:YES completion:nil];
}
- (void)reachabilityChanged:(NSNotification *)note
{
Reachability *reach = [note object];
if ([reach isReachable] && !initialNetworkStatus) {
[internetIndicator setImage:[UIImage imageNamed:#"online_indicator.png"]];
// Start syncing local records with the remote server
[[BTSyncEngine sharedEngine] startSync];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Sync in progress";
}
}
#end