NSNotificationCenter, blocks and ivar of type SEL: can't get it to work - objective-c

I'm trying the following:
Whenever an OAuth token changes, I want to update some of my view controllers. The view controllers I want to update all inherit from a view controller. This view controller will listen for notifications whenever a new access token is set or whenever the access token is cleared. Whenever the access token is set or cleared, I want to set a selector to a method that should be executed once the view controller will be displayed (i.e. on -viewWillAppear:).
Somehow the blocks inside the addObserverForName:object:queue:usingBlock: don't seem to get called. I can't get it to log even. Because of this the selectors never change. From what I've read using the __block attribute on an ivar should allow the ivar to be changed from within a block.
#interface SDViewController ()
- (void)reloadData;
- (void)clearData;
#end
#implementation SDViewController
{
__block SEL selectorOnViewWillAppear;
}
- (id)initWithDataSource:(id <SDViewControllerDataSource>)dataSource
{
self = [super init];
if (self)
{
selectorOnViewWillAppear = #selector(reloadData);
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"view did load");
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenChangedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test1");
selectorOnViewWillAppear = #selector(reloadData);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenClearedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (selectorOnViewWillAppear)
[self performSelector:selectorOnViewWillAppear];
}
- (void)reloadData
{
NSLog(#"reloadData");
selectorOnViewWillAppear = nil;
}
- (void)clearData
{
NSLog(#"clearData");
selectorOnViewWillAppear = nil;
}
#end

Fixed it by changing the observer code from this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:self
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
To this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
The Apple documentation related to this issue is found here. The object in the
-addObserverForName:object:queue:usingBlock: method should be the object that generates the notifications. Setting the object to nil fixed the issue.

Related

AdMob interstitial memory leak? Sprite kit

I have problem with AdMob and sprite kit.
Everytime ad will show or load, it will increase memory usage a lot!
When EndGameScene open it will call "showAd", and when replay button is pressed "loadAd".
Ads works well.
When game starts memory usage is something like 50mb and few ads later it is at least 70-100mb!
So what i do wrong:
GameViewController.m
- (void)viewDidLoad{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"showAd" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"loadAd" object:nil];
self.interstitial = [self createAndLoadInterstitial];
self.interstitial = [[GADInterstitial alloc] initWithAdUnitID:#"ca-app-pub-<id>"];
GADRequest *request = [GADRequest request];
// Requests test ads on test devices.
request.testDevices = #[#"0000000"];
[self.interstitial loadRequest:request];
//etc
}
- (void)handleNotification:(NSNotification *)notification{
if ([notification.name isEqualToString:#"showAd"]) {
if (self.interstitial.isReady) {
[ self.interstitial presentFromRootViewController:self];
}
else {
}
}
if ([notification.name isEqualToString:#"loadAd"]) {
self.interstitial = [self createAndLoadInterstitial];
}
}
- (GADInterstitial *)createAndLoadInterstitial {
GADInterstitial *interstitial =
[[GADInterstitial alloc] initWithAdUnitID:#"ca-app-pub-<id>"];
interstitial.delegate = self;
[interstitial loadRequest:[GADRequest request]];
return interstitial;}
Thanks!
You are creating everytime a new GADInterstitial object, which starts a load request. I guess because it is waiting for an answer it will never be released from memory again.
A better way would be to store the GADInterstitial in a global property and just call a new loadRequest, if you need a new ad.

Test NSNotification delivery

I'm trying to make sure the NSNotification gets sent after reportIssue is called.
I get this error:
error: -[APHIssueComposerTests testPopulatedIssueIsReceived] : OCMockObject[APHIssueComposerTests]: expected method was not invoked: reportIssueNotificationReceived
In APHIssueComposer.m:
- (void) reportIssue {
APHIssue* issue = [self issue];
NSNotification* notification = [NSNotification notificationWithName:APHLogDataObjectNotification object:issue];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
[self discardIssue];
}
In APHIssueComposerTests.m:
- (void)setUp
{
[super setUp];
self.mockObserver = [OCMockObject mockForClass:[self class]];
[[NSNotificationCenter defaultCenter] addObserver:self.mockObserver
selector:#selector(reportIssueNotificationReceived)
name:APHLogDataObjectNotification
object:nil];
self.issueComposer = [[APHIssueComposer alloc] initWithTempDirectory:#"/my/fake/directory"];
}
- (void)testPopulatedIssueIsReceived
{
[[self.mockObserver expect] reportIssueNotificationReceived];
self.issueComposer.message = #"fake message.";
[self.issueComposer reportIssue];
[mockObserver verify];
[[NSNotificationCenter defaultCenter] removeObserver:mockObserver name:APHLogDataObjectNotification object:nil];
}
- (void)tearDown
{
[super tearDown];
[[NSNotificationCenter defaultCenter] removeObserver:mockObserver name:APHLogDataObjectNotification object:nil];
}
Why doesn't the mock object receive the notification?
The problem is that enqueueNotification is asynchronous.

use correctly notificationCenter addObserverForName with blocks

How can I, in clean way, instantiate 2 BOOL variables in a block ?
As follow, it's working but I have "Capturing 'self' strongly in this block is likely to lead to a retain cycle", which obviously is not good...
[notificationCenter addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:mainQueue usingBlock:^(NSNotification *note) {
isApplicationOnForegroundMode = NO;
isApplicationOnBackgroundMode = YES;
} ];
[notificationCenter addObserverForName:UIApplicationDidBecomeActiveNotification
object:nil
queue:mainQueue usingBlock:^(NSNotification *note) {
isApplicationOnForegroundMode = YES;
isApplicationOnBackgroundMode = NO;
} ];
I presume that isApplicationOnForegroundMode and isApplicationOnBackgroundMode are ivars.
You'll need to add a couple of ivars or properties to track the observation blocks so you can remove them. I'll call those id properties backgroundObserver and activeObserver.
Update your code to:
__unsafe_unretained <<self's class>> *this = self; // or __weak, on iOS 5+.
self.backgroundObserver = [notificationCenter
addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:mainQueue
usingBlock:^(NSNotification *note) {
this->isApplicationOnForegroundMode = NO;
// or: this.isApplicationOnForegroundMode = YES, if you have a property declared
this->isApplicationOnBackgroundMode = YES;
} ];
self.activeObserver = [notificationCenter
addObserverForName:UIApplicationDidBecomeActiveNotification
object:nil
queue:mainQueue usingBlock:^(NSNotification *note) {
this->isApplicationOnForegroundMode = YES;
this->isApplicationOnBackgroundMode = NO;
} ];
You will also need to make sure that you call
[[NSNotificationCenter defaultCenter] removeObserver:self.backgroundObserver];
[[NSNotificationCenter defaultCenter] removeObserver:self.activeObserver];
in -dealloc.

NSNotificationCenter is not sending out notifications

I am developing a system to keep track of achievements and for that I use NSNotificationCenter. When an achievement is unlocked in a object in the app a notification is sent to JMAchievementHandler which sets a string to YES and saves the progress in NSUserDefaults. My problem is that the notifications are not working probably. Here is my code in JMAchievementHandler:
- (id)init {
self = [super init];
if (self) {
//Set strings to NO
achievementOne = #"NO";
achievementTwo = #"NO";
achievementThree = #"NO";
achievementFour = #"NO";
//Add observers
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName:) name:#"achievement1" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement2" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement3" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement4" object:nil];
//Add observer to observe delegate methods
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationLaunch" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationEnterBackground" object:nil];
}
return self;
}
- (void)receiveNotificationWithName:(NSNotification *)notification {
if ([[notification name] isEqualToString:#"achievement1"] || [achievementOne isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementOne = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement2"] || [achievementTwo isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementTwo = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement3"] || [achievementThree isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementThree = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement4"] || [achievementFour isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementFour = #"YES";
}
}
- (void)receiveNotificationFromDelegate:(NSNotification *)notificationDelegate
{
if ([[notificationDelegate name] isEqualToString:#"notificationLaunch"]) {
[self loadDataOnLaunch];
}
else if ([[notificationDelegate name] isEqualToString:#"notificationEnterBackground"]) {
[self saveDataOnExit];
}
}
- (void)loadDataOnLaunch
{
NSLog(#"loadDataOnLaunch");
}
- (void)saveDataOnExit
{
NSLog(#"saveDataOnExit");
}
When I try to post a notification the NSLogs are not called. I use the following code to send notifications from my AppDelegate and ViewController.
- (void)achievement1ButtonPressed:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"achievement1" object:self userInfo:nil];
}
I hope you guys can help me out. Thanks a lot
Jonas
Do you even have a method named receiveNotificationWithName? When you entered the code, the autocomplete should have barfed on that, offering receiveNotificationWithName: instead (unless you wrote it first, without NSNotification*, then added it later...
Anyway, there is a big difference between the two. Also, your handler is buggy. You should revisit that code.
There are lots of reasons to prefer blocks, and this points to one of them. You can just put your code right in the registration, and not worry about giving the wrong selector.
[[NSNotificationCenter defaultCenter] addObserverForName:#"achievement1"
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
}];
These look like one-shot notifications, so if you want to unregister the notification handler after it runs one time, do this (note the __block).
__block id achievement1 = [[NSNotificationCenter defaultCenter]
addObserverForName:#"achievement1 ":^{
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
// Remove observer from notification center
[[NSNotificationCenter defaultCenter] removeObserver:achievement1];
}];
All of this looks like it should work (besides the colon, like Andrea mentioned). Is it possible that your button callback isn't being called for a similar reason? I would add an NSLog in your achievement1ButtonPressed: method to make sure.
What I can see is that only one of your addObesrver method call the right selector with the ":", thus the right method signature.

MPMoviePlayerController prepareToPlay not working with HTTP Live Streaming

I'm trying to play an HTTP live streaming video using a MPMoviePlayerController object, inside a modal view controller. If I run the code like this, everything works fine. I get a MPMoviePlaybackStatePlaying notification, and the video plays.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
NSURL *url = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayerController = [[[MPMoviePlayerController alloc] initWithContentURL:url] autorelease];
self.moviePlayerController.view.frame = CGRectMake(0,0,320,416);
self.moviePlayerController.controlStyle = MPMovieControlStyleNone;
self.moviePlayerController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.moviePlayerController.view];
self.moviePlayerController.fullscreen = NO;
[self.moviePlayerController play];
}
I would prefer to use the prepareToPlay method, so that I can show a loading indicator, and for a nicer experience. However, I don't seem to be able to get this working. No MPMoviePlayerPlaybackStateDidChangeNotification ever gets called, and the stream doesn't play. The activity indicator just sits there spinning forever.
I've confirmed that prepareToPlay does get called, but nothing else seems to happen after that. I've also confirmed that this code works fine with a local movie file. It just seems to be with HTTP Live Streaming which fails. Can anyone see what I'm doing wrong?
- (void)viewDidLoad {
[super viewDidLoad];
[self.activityIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
NSURL *url = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayerController = [[[MPMoviePlayerController alloc] initWithContentURL:url] autorelease];
[self registerForMovieNotifications];
}
- (void)registerForMovieNotifications {
//movie is ready to play notifications
if ([self.moviePlayerController respondsToSelector:#selector(loadState)]) {
//this is a 3.2+ device
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerLoadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
self.moviePlayerController.movieSourceType = MPMovieSourceTypeStreaming;
[self.moviePlayerController prepareToPlay];
} else {
//pre-3.2 device
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePreloadDidFinish:) name:MPMoviePlayerContentPreloadDidFinishNotification object:nil];
}
//movie has finished notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
- (void) moviePlayerPlaybackStateDidChange:(NSNotification*)notification {
NSLog(#"playbackDidChanged");
MPMoviePlayerController *moviePlayer = notification.object;
MPMoviePlaybackState playbackState = moviePlayer.playbackState;
if(playbackState == MPMoviePlaybackStateStopped) {
NSLog(#"MPMoviePlaybackStateStopped");
} else if(playbackState == MPMoviePlaybackStatePlaying) {
NSLog(#"MPMoviePlaybackStatePlaying");
} else if(playbackState == MPMoviePlaybackStatePaused) {
NSLog(#"MPMoviePlaybackStatePaused");
} else if(playbackState == MPMoviePlaybackStateInterrupted) {
NSLog(#"MPMoviePlaybackStateInterrupted");
} else if(playbackState == MPMoviePlaybackStateSeekingForward) {
NSLog(#"MPMoviePlaybackStateSeekingForward");
} else if(playbackState == MPMoviePlaybackStateSeekingBackward) {
NSLog(#"MPMoviePlaybackStateSeekingBackward");
}
}
//3.2+ devices
- (void)moviePlayerLoadStateChanged:(NSNotification*)notification {
NSLog(#"load state changed");
//unless state is unknown, start playback
if ([self.moviePlayerController loadState] != MPMovieLoadStateUnknown) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
self.moviePlayerController.view.frame = CGRectMake(0,0,320,416);
self.moviePlayerController.controlStyle = MPMovieControlStyleNone;
self.moviePlayerController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.moviePlayerController.view];
self.moviePlayerController.fullscreen = NO;
[self.moviePlayerController play];
}
}
//pre 3.2 devices
- (void) moviePreloadDidFinish:(NSNotification*)notification {
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerContentPreloadDidFinishNotification object:nil];
[self.moviePlayerController play];
}
I don't know why, but I was able to solve this by moving the line self.moviePlayerController.controlStyle = MPMovieControlStyleNone; out of the moviePlayerLoadStateChanged: method, and into the viewDidLoad method.
Anyone have any ideas why this made a difference?