I'm still new to blocks in objective-c and wondering if I have this psuedo code correct. I'm not sure if it's enough to just remove the observer or if i have to call removeObserver:name:object:
-(void) scan {
Scanner *scanner = [[Scanner alloc] init];
id scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:#"ScanComplete"
object:scanner
queue:nil
usingBlock:^(NSNotification *notification){
/*
do something
*/
[[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
[scanner release];
}];
[scanner startScan];
}
Update: I'm receiving intermittent EXC_BAD_ACCESS from this block, so this can't be right.
Declare the scanComplete variable before defining the block itself.
The reason why you need to do this is because you're trying to access a variable that doesn't exist within the block at the time of definition since the variable itself has not been assigned yet.
What is EXC_BAD_ACCESS? Well, it's an exception that is thrown when you try to access a reference that doesn't exist. So that is exactly the case in your example.
So if you declare the variable before the block itself, then it should work:
-(void) scan {
Scanner *scanner = [[Scanner alloc] init];
__block id scanComplete;
scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:#"ScanComplete"
object:scanner
queue:nil
usingBlock:^(NSNotification *notification){
/*
do something
*/
[[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
[scanner release];
}];
[scanner startScan];
}
You should not unregister in the register block. Instead, store the token returned from addObserverForName (in this case, your scanComplete) as an instance variable or in a collection that is an instance variable, and unregister later when you're about to go out of existence (e.g. in dealloc). What I do is keep an NSMutableSet called observers. So:
id ob = [[NSNotificationCenter defaultCenter]
addObserverForName:#"whatever" object:nil queue:nil
usingBlock:^(NSNotification *note) {
// ... whatever ...
}];
[self->observers addObject:ob];
And then later:
for (id ob in self->observers)
[[NSNotificationCenter defaultCenter] removeObserver:ob];
self->observers = nil;
Apple Document about this method:
The following example shows how you can register to receive locale change notifications.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
self.localeChangeObserver = [center addObserverForName:NSCurrentLocaleDidChangeNotification object:nil
queue:mainQueue usingBlock:^(NSNotification *note) {
NSLog(#"The user's locale changed to: %#", [[NSLocale currentLocale] localeIdentifier]);
}];
To unregister observations, you pass the object returned by this method to removeObserver:. You must invoke removeObserver: or removeObserver:name:object: before any object specified by addObserverForName:object:queue:usingBlock: is deallocated.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self.localeChangeObserver];
The scope of the block doesn't have permission to release the scanner object. If you're not using garbage collection, removing the release and making the scanner autorelease ([[[Scanner alloc] init] autorelease]) should do the trick.
You should also be able to safely move the call to removeObserver outside of the block.
For the case of EXC_BAD_ACCESS: Entering bt in the console window after the application crashes will give you a backtrace, and should inform you where the error occurred.
Related
Trying to get one ViewController to communicate with another through standard Cocoa Notifications.
Wrote a simple test case. In my initial VC I add the following to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector: #selector(mesageReceived:)
name:#"test.channel.pw" object:nil];
I add the following method:
- (void)mesageReceived:(NSNotification *)notif
{
NSString *text = (NSString *)notif;
}
I then segue to a VC which has the following added to its viewDidLoad:
NSString *test = #"someText";
[[NSNotificationCenter defaultCenter]
postNotificationName:#"test.channel.pw" object:test];
When I run it, the notification works - the messageReceived method is called - but the value of text is "test.channel.pw", not "someText" as I expected.
What is going on here, and what is the correct syntax for passing a reference to a class instance using notifications?
You need to pass your data using userInfo dictionary.
Change the notification passing code like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"test.channel.pw" object:nil userInfo:[NSDictionary dictionaryWithObject:#"someText" forKey:#"dataKey"]];
And change the receiver method like:
- (void)mesageReceived:(NSNotification *)notif
{
NSString *text = [[notif userInfo] objectForKey:#"dataKey"];
}
Try NSString *text = (NSString *)[notif object];.
By this
-(void)someMethod{
NSDictionary *postDict = #{#"Key":[NSNumber numberWithFloat:17.0]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"myNotification" object:postDict];
}
Some other class
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter]postNotificationName:#"myNotification" object:nil userInfo:postDict];
}
-(void)valueForNotification:(NSNotification *)notification{
NSDictionary *dict = [notification userInfo];
NSLog(#"%#",[dict objectForKey:#"Key"]); //prints out 17.0
}
You need to pass only NSDictionary object in NSnotificationCenter. Through NSDictionary you can pass any data and then you need to parse once received.
I have seen this around a few times but can't seem to find what is the difference between the two ...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo:)
name:FBSDKProfileDidChangeNotification
object:nil];
- (void)loginViewFetchedUserInfo:(NSNotification *)notification
and
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo)
name:FBSDKProfileDidChangeNotification
object:nil];
- (void)loginViewFetchedUserInfo
I know that (void)methodname:(TYPE *)newName can pass in a value to the method but I don't know what the difference is in the two above and why you would do the first one (which is used in the Facebook SDK example) over the second one.
The first method passes the NSNotification object to the method. This way allows you to access information about the notification.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewFetchedUserInfo:)
name:FBSDKProfileDidChangeNotification
nil];
For example, if the notification was posted with a userInfo dictionary
NSDictionary *userInfo = #{#"Blah" : #"foo"};
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKProfileDidChangeNotification object:self userInfo:userInfo];
and you wanted to access userInfo in the method. You can also access the sender of the notification, which would be the notification's object.
- (void)loginViewFetchedUserInfo:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
NSObject *sender = notification.object;
}
I have a code like the following:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:nil userInfo:userInfo];
In the selector method:
- (void)itemPlayEnded:(NSNotification *)notification
{
NSLog(#"Entered itemPlayEnded");
AVPlayerItem *p = [notification object];
NSLog(#"userinfo description %#",[[notification userInfo] description]);
}
The first time the itemPlayEnded is accessed the userInfo is non-null. After the first time the value is null.
Can anybody tell me why this is happening? Why null value for userInfo after the first time?
EDIT:
I need to clarify what is happening. I also updated my Notification code to use the lastItem which is a AVPlayerItem.
queuePlayer = [[AVQueuePlayer alloc] init];
NSDictionary *userInfo = #{#"tones": copyoftones};
for (NSString *playThis in listOfTonesToBePlayed) {
NSString *soundPath =[[NSBundle mainBundle] pathForResource:playThis ofType:#"mp3"];
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:soundURL];
lastItem = thePlayerItemA;
[queuePlayer insertItem:thePlayerItemA afterItem:nil];
}
queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:lastItem];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:lastItem userInfo:userInfo];
[queuePlayer play];
What happens.
before the queuePlayer is able to play a tone the itemPlayEnded is entered along with the Dictionary non nil
Next the list of tones are played
The itemPlayEnded is re-entered with the Dictionary nil.
I wanted to use code to reset the Notification inside of the itemPlayEnded method with something like the following (p is a AVPlayerItem which is the lastItem from the code with the Notification):
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:p];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemPlayEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:p];
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:p userInfo:userInfo];
[queuePlayer play];
But then the itemPlayEnded method is re-entered in a never-ending loop without playing the player. A race condition.
Any suggestions?
EDIT 2:
I have determined that the offending code is:
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:lastItem userInfo:userInfo];
some how I need to set the object to use the correct sender. Not sure yet on what to put there.
EDIT 3:
What I really wanted to do was be able to loop thru the complete sequence of sounds or mp3s. I finally figured that what I was trying to do here was not going to work like I wanted it. So, I ended up using the class from https://github.com/dgiovann/AVQueuePlayerPrevious and this worked out great for me!
You are subscribing to the AVPlayerItemDidPlayToEndTimeNotification which is being posted first by yourself (with some user info) and then posted again, this time NOT by you and therefore may or may not contain any user info. Refer to the documentation to see when and why Apple will post this notification and what information you can expect with it.
https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVPlayerItem_Class/Reference/Reference.html
I am using MPMoviePlayer in my project. I have registered for the movie player finish notifications and it is working good. I am displaying an error alert whenever a notification is received for movie player error. But the problem is that the error alert displays multiple times. It happens because more than one notifications are received for same error and that too at the same time. I have tried using boolean variables to control the alert display but since the notifications are received at the same time, it is not working. What approach should I apply, please suggest.
My code for notification method:
MPMovieFinishReason reason = [[[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
NSError *errorMsg = [[notification userInfo] valueForKey:#"error"];
NSString *errmsg = [errorMsg localizedDescription];
if (reason == 1 && !errorReceived){
NSError *errorMsg = [[notification userInfo] valueForKey:#"error"];
NSString *errmsg = [errorMsg localizedDescription];
[self showErrorAlert];
}
For registering notification:
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(moviePlayerDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.player];
for removing observer, in viewWillDisappear
[[NSNotificationCenter defaultCenter]removeObserver:self];
Remove the observer once you get the error
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player];
and add the observer once you click to play .
I have a table view controller that is supposed to be populated with data from an array that is encapsulated within a store class. The table needs to know how many rows are in each section via the method table:numberOfRowsInSection:. In this method, I need to return the size of the array that is inside my instance of store. I initially did this by making store a singleton, but was told this was inefficient and that using NSNotificationCenter would be better.
As far as I know, all NSNotificationCenter does is trigger methods in certain objects when another object posts a specific notification. How can I use NSNotificationCenter to send the size of the array to my table view controller?
You can do it like this:
...
// Send
[[NSNotificationCenter defaultCenter] postNotificationName: SizeOfRrrayNotification
object: [NSNumber numberWithInteger: [array count]]];
...
// Subscribe
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(sizeOfArray:)
name: SizeOfRrrayNotification
object: nil];
// Get size
- (void) sizeOfArray: (NSNotification*) notification
{
NSNumber* sizeOfArray = (NSNumber*) notification.object;
NSLog(#"size of array=%i", [sizeOfArray integerValue]);
}
Post the Notification :
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyArraySize" object: [NSNumber numberWithInteger: [myArray count]]] userInfo:nil];
Get the Notification :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getSizeOfArray:) name:#"MyArraySize" object:nil];
Add this method in the viewController where you are getting notification:
- (void) getSizeOfArray: (NSNotification*) notification
{
NSNumber* myArraySize = (NSNumber*) notification.object;
}
you can even send more data through "userInfo" and get that data in selector method using notification.userInfo, but remember its type is "NSDictionary"
Hope this will help you.
The method in which you are calculating the size of the array:
<------Notification sending side---------->
-(void)sizeOfArray{
int size = [myArray count];
NSMutableString *myString = [NSMutable string];
NSString *str = [NSString stringwithFormat:#"%d",size];
[myString apprndString:str];
//It is to be noted that NSNotification always uses NSobject hence the creation of mystring,instead of just passing size
[[NSNotificationCenter defaultCenter] postNotificationName:#"GetSizeOfArray" object:myString];
}
Now once you have posted a notification,add this to the viewDidLoad method of the controller where you are sending the data
<------Notification receiving side---------->
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(get_notified:) name:#"GetSizeOfArray" object:nil];
//The selector method should always have a notification object as its argument
[super viewDidLoad];
}
- (void)get_notified:(NSNotification *)notif {
//This method has notification object in its argument
if([[notif name] isEqualToString:#"GetSizeOfArray"]){
NSString *temp = [notif object];
int size = [temp int value];
//Data is passed.
[self.tableView reloadData]; //If its necessary
}
}
Hope this helps.