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.
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 ServiceFacade class with class methods for communicating with backend services.
On that ServiceFacade class I have a static method that returns NSMutableDictionary in which I keep current ServiceFacade's downloading operations.
I want to observer changes on this NSMutableDictionary either in AppDelegate or any other place. The app delegate seems not to respond to
- (void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context{
}
Method for returning NSMutableDictionary:
+(NSMutableDictionary *)downloadOperations
{
if (_downloadOperations)
{
return _downloadOperations;
}
[_downloadOperations addObserver:[AppDelegate sharedAppDelegate] forKeyPath:#"downloadOperationsDict" options:0 context:NULL];
_downloadOperations = [NSMutableDictionary dictionary];
return _downloadOperations;
}
Any idea ?
There is no way to observe NSMutableDictionary changes. But there are 2 workarounds
1) Subclass NSMutableDictionary and trigger Notifications on setObject:forKey and removeObjectForKey.
2) Wrap your _downloadOperations write/remove operations and trigger notifications there.
I suggest you to use 2) variant, as subclassing NSMutableDictionary is not so easy.
So 2) variant will be like this.
Add these 2 methods to class ServiceFacade
- (void)setDownloadObject:(id)aObj forKey:(id<NSCopying>)aKey
{
[self.downloadOperations setObject:aObj forKey:aKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadOperationsChanged" object:self
userInfo:self.downloadOperations];
}
- (id)removeDownloadObjectForKey:(id<NSCopying>)aKey
{
[[self.downloadOperations] removeObjectForKey:aKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadOperationsChanged" object:self
userInfo:self.downloadOperations];
}
After this, you need to add and remove objects from that dictionary via this 2 methods. And you can also subscribe for changes
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(downloadOperationsChanged:)
name:#"DownloadOperationsChanged"
object:nil];
return YES;
}
- (void)downloadOperationsChanged:(NSNotification *)aNotification
{
NSLog(#"Operations : %#", aNotification);
}
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'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.