Key Value Observe static NSDictionary on facade class - ios

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);
}

Related

How to access data passed in iOS notification (simple)?

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.

The difference between these two scheduled method calls - iOS

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;
}

How to get a non null userInfo after the first time to my #selector(method)

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

How to transfer data ViewControllers/classes using NSNotificationCenter?

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.

How to detect changes in NSUserDefault?

I have switches and I want to detect if any of switch was changed position, if changes was made I need to start my action.
Switches stores position in NSUserDefaults
- (IBAction)saveSwitch:(id)sender
{
NSUserDefaults *defs1 = [NSUserDefaults standardUserDefaults];
[defs1 setBool: blackSwitch.on forKey: #"blackKey"];
NSUserDefaults *defs2 = [NSUserDefaults standardUserDefaults];
[defs2 setBool: greenSwitch.on forKey: #"greenKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
You can post a notification whenever you call synchronize
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyAppSettingsChanged" object:self userInfo:nil];
Then in your other class listen to the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onAppSettingsChanged:) name:#"MyAppSettingsChanged" object:nil];
-(void) onAppSettingsChanged:(NSNotification)notification
{
// settings changed
}
If you want, you can pass an NSDictionary into userInfo when calling postNotificationName that contains information like which settings have changed.
If you're using NSUserDefaults the easiest is to subscribe NSUserDefaultsDidChangeNotification. It is automatically sent when something changes.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appSettingsDidChange:)
name:NSUserDefaultsDidChangeNotification
object:nil];
The best way to track changes to NSUserDefaults is to add an observer using KVO. This way you do not need to perform any custom notification code or track changes manually.
In the class that wants to be informed about the changes just register it as a listener to the specified keys:
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"blackKey" options:NSKeyValueObservingOptionNew context:nil];
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"greenKey" options:NSKeyValueObservingOptionNew context:nil];
Then just respond to the notification:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (object == defaults) {
// Here you can grab the values or just respond to it with an action.
}
}
Now whenever one of those keys changes you will be notified automatically.
This is a super clean solution and allows for some heavy reuse. For example, if you add the NSKeyValueObservingOptionInitial key to the options parameter above (NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) then it will also notify your observer method with the initial value, allowing you to reuse that method even for initial states.
Swift Version
Setting up the defaults:
NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "blackKey", options: .New, context: nil)
NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "greenKey", options: .New, context: nil)
The observer:
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if object is NSUserDefaults {
// Here you can grab the values or just respond to it with an action.
}
}
You cannot detect changes in NSUserDefaults. Instead, track when the switch itself is changed, and handle that event. Example:
[blackSwitch addTarget:self
action:#selector(blackSwitchChanged:)
forControlEvents:UIControlEventValueChanged];
Handle the switch position changing:
- (IBAction)blackSwitchChanged:(id)sender {
NSLog(#"Black switch changed");
..
// check if blackSwitch is on or off.
}

Resources