I am attempting to accessing related artists/playlists, etc using the SPSearch class as follows, but upon examining the logs it appears that the search never finishes loading:
self.search = [SPSearch searchWithSearchQuery:self.artist inSession:[SPSession sharedSession]];
[SPAsyncLoading waitUntilLoaded:self.search timeout:100.0 then:^(NSArray *loadedItems, NSArray *notLoadedItems){
NSLog(#"Search completed, Loaded items = %d, unloaded items = %d", [loadedItems count], [notLoadedItems count]);
}];
I saw a similar question SPSearch in CocoaLibSpotify but the Key-Value-observer appears to be overkill/convoluted for what I would like to do(maybe it is just my relative newness to iOS dev that makes the KVO seem as overkill/convoluted). Even if it isn't overkill, how would I go about using the SPSearch class to accomplish what I would like to do? It looks like it should be straight forward, but I appear to be getting hung up with some devilish details(search never loads, artist/playlists arrays returning count of of 0, etc).
Edit:
Additionally, I attempted to give the KVO a shot, as opposed to the SPAsyncLoading, but it it has been 10 minutes, and no change seems to have been observed:
[self addObserver:self
forKeyPath:#"search.loaded"
options:0
context:nil];
...
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"search.loaded"]) {
if (!self.search.isLoaded)
return;
NSLog(#"artists length %d", self.search.artists.count);
NSLog(#"playlistslength %d", self.search.playlists.count);
NSLog(#"albums length %d", self.search.albums.count);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Also, I can rule out a network related issue, I am over Wifi with solid data rates and authentication happens flawlessly.
Thanks.
Related
This might be an awful bug in iOS 9.3 (release).
When adding a single observer to [NSUserDefaults standardUserDefaults] I've noticed that the responding method -observeValueForKeyPath:ofObject:change:context: is called multiple times.
In the simple example below, every time a UIButton is pressed once, observeValueForKeyPath fires twice. In more complicated examples it fires even more times. It is only present on iOS 9.3 (both on sim and devices).
This can obviously wreak havoc on an app. Anyone else experiencing the same?
// ViewController.m (barebones, single view app)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"SomeKey" options:NSKeyValueObservingOptionNew context:NULL];
}
- (IBAction)buttonPressed:(id)sender {
NSLog(#"buttonPressed");
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"SomeKey"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(#"observeValueForKeyPath: %#", keyPath);
}
Yes I am experiencing this as well and it seems to be a bug, below is a quick workaround I’m using for the moment until this is fixed. I hope it helps!
Also to clarify, since iOS 7 KVO has been working great with NSUserDefaults and it certainly appears to be key value observable as Matt stated, it is explicitly written in NSUserDefaults.h in the iOS 9.3 SDK: “NSUserDefaults can be observed using Key-Value Observing for any key stored in it."
#include <mach/mach.h>
#include <mach/mach_time.h>
#property uint64_t newTime;
#property uint64_t previousTime;
#property NSString *previousKeyPath;
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
//Workaround for possible bug in iOS 9.3 SDK that is causing observeValueForKeyPath to be called multiple times.
newTime = mach_absolute_time();
NSLog(#"newTime:%llu", newTime);
NSLog(#"previousTime:%llu", previousTime);
//Try to avoid duplicate calls
if (newTime > (previousTime + 5000000.0) || ![keyPath isEqualToString:previousKeyPath]) {
if (newTime > (previousTime + 5000000.0)) {
NSLog(#"newTime > previousTime");
previousTime = newTime;
NSLog(#"newTime:%llu", newTime);
NSLog(#"previousTime:%llu", previousTime);
}
if (![keyPath isEqualToString:previousKeyPath]) {
NSLog(#"new keyPath:%#", keyPath);
previousKeyPath = keyPath;
NSLog(#"previousKeyPath is now:%#", previousKeyPath);
}
//Proceed with handling changes
if ([keyPath isEqualToString:#“MyKey"]) {
//Do something
}
}
}
When adding a single observer to [NSUserDefaults standardUserDefaults] I've noticed that the responding method -observeValueForKeyPath:ofObject:change:context: is called multiple times
This is a known issue and is reported (by Apple) as fixed in iOS 11 and macOS 10.13.
Adding this answer for MacOS (10.13) which definitely has the bug getting multiple notifications for KVO of NSUserDefault Keys, and which also addresses deprecations. It is better to use a calculation for elapsed nano seconds that gets it for the machine you are running on. Do it like so:
#include <mach/mach.h>
#include <mach/mach_time.h>
static mach_timebase_info_data_t _sTimebaseInfo;
uint64_t _newTime, _previousTime, _elapsed, _elapsedNano, _threshold;
NSString *_previousKeyPath;
-(BOOL)timeThresholdForKeyPathExceeded:(NSString *)key thresholdValue:(uint64_t)threshold
{
_previousTime = _newTime;
_newTime = mach_absolute_time();
if(_previousTime > 0) {
_elapsed = _newTime - _previousTime;
_elapsedNano = _elapsed * _sTimebaseInfo.numer / _sTimebaseInfo.denom;
}
if(_elapsedNano > threshold || ![key isEqualToString:_previousKeyPath]) {
if(![key isEqualToString:_previousKeyPath]) _previousKeyPath = key;
return YES;
}
return NO;
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(![self timeThresholdForKeyPathExceeded:keyPath thresholdValue:5000000]) return; // Delete this line of MacOS bug ever fixed
}
// Else this is the KeyPath you are looking for Obi Wan, process it.
}
This is based on Listing 2 of this Apple Doc:
https://developer.apple.com/library/content/qa/qa1398/_index.html
This might be an awful bug in iOS 9.3 (release).
When adding a single observer to [NSUserDefaults standardUserDefaults] I've noticed that the responding method -observeValueForKeyPath:ofObject:change:context: is called multiple times.
In the simple example below, every time a UIButton is pressed once, observeValueForKeyPath fires twice. In more complicated examples it fires even more times. It is only present on iOS 9.3 (both on sim and devices).
This can obviously wreak havoc on an app. Anyone else experiencing the same?
// ViewController.m (barebones, single view app)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"SomeKey" options:NSKeyValueObservingOptionNew context:NULL];
}
- (IBAction)buttonPressed:(id)sender {
NSLog(#"buttonPressed");
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"SomeKey"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(#"observeValueForKeyPath: %#", keyPath);
}
Yes I am experiencing this as well and it seems to be a bug, below is a quick workaround I’m using for the moment until this is fixed. I hope it helps!
Also to clarify, since iOS 7 KVO has been working great with NSUserDefaults and it certainly appears to be key value observable as Matt stated, it is explicitly written in NSUserDefaults.h in the iOS 9.3 SDK: “NSUserDefaults can be observed using Key-Value Observing for any key stored in it."
#include <mach/mach.h>
#include <mach/mach_time.h>
#property uint64_t newTime;
#property uint64_t previousTime;
#property NSString *previousKeyPath;
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
//Workaround for possible bug in iOS 9.3 SDK that is causing observeValueForKeyPath to be called multiple times.
newTime = mach_absolute_time();
NSLog(#"newTime:%llu", newTime);
NSLog(#"previousTime:%llu", previousTime);
//Try to avoid duplicate calls
if (newTime > (previousTime + 5000000.0) || ![keyPath isEqualToString:previousKeyPath]) {
if (newTime > (previousTime + 5000000.0)) {
NSLog(#"newTime > previousTime");
previousTime = newTime;
NSLog(#"newTime:%llu", newTime);
NSLog(#"previousTime:%llu", previousTime);
}
if (![keyPath isEqualToString:previousKeyPath]) {
NSLog(#"new keyPath:%#", keyPath);
previousKeyPath = keyPath;
NSLog(#"previousKeyPath is now:%#", previousKeyPath);
}
//Proceed with handling changes
if ([keyPath isEqualToString:#“MyKey"]) {
//Do something
}
}
}
When adding a single observer to [NSUserDefaults standardUserDefaults] I've noticed that the responding method -observeValueForKeyPath:ofObject:change:context: is called multiple times
This is a known issue and is reported (by Apple) as fixed in iOS 11 and macOS 10.13.
Adding this answer for MacOS (10.13) which definitely has the bug getting multiple notifications for KVO of NSUserDefault Keys, and which also addresses deprecations. It is better to use a calculation for elapsed nano seconds that gets it for the machine you are running on. Do it like so:
#include <mach/mach.h>
#include <mach/mach_time.h>
static mach_timebase_info_data_t _sTimebaseInfo;
uint64_t _newTime, _previousTime, _elapsed, _elapsedNano, _threshold;
NSString *_previousKeyPath;
-(BOOL)timeThresholdForKeyPathExceeded:(NSString *)key thresholdValue:(uint64_t)threshold
{
_previousTime = _newTime;
_newTime = mach_absolute_time();
if(_previousTime > 0) {
_elapsed = _newTime - _previousTime;
_elapsedNano = _elapsed * _sTimebaseInfo.numer / _sTimebaseInfo.denom;
}
if(_elapsedNano > threshold || ![key isEqualToString:_previousKeyPath]) {
if(![key isEqualToString:_previousKeyPath]) _previousKeyPath = key;
return YES;
}
return NO;
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(![self timeThresholdForKeyPathExceeded:keyPath thresholdValue:5000000]) return; // Delete this line of MacOS bug ever fixed
}
// Else this is the KeyPath you are looking for Obi Wan, process it.
}
This is based on Listing 2 of this Apple Doc:
https://developer.apple.com/library/content/qa/qa1398/_index.html
I'm dealing with a tricky situation in my app. This app shows a list of files in a UITableView, from there you can download (I use AFnetworking 2.0) a file and then you can see download progress in another UITableView (all the views are in a UITabBarController).
My problem is that I'm not really sure about how to reload the UITableVIew showing current downloads when a new one is added or when one is finished or cancelled. I tried using KVO but it didn't work observing the operation queue from AFnetworking.
EDIT:
This is the KVO code I tried
In my downloads UITableViewCell
- (void)viewDidLoad
{
[super viewDidLoad];
[[[[PodcastManager sharedManager] sessionManager] operationQueue] addObserver:self
forKeyPath:NSStringFromSelector(#selector(operations))
options:NSKeyValueObservingOptionNew
context:nil];
}
and then...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == [[[PodcastManager sharedManager] sessionManager] operationQueue]) {
if ([keyPath isEqualToString:NSStringFromSelector(#selector(operations))]) {
NSLog(#"OperationCount: %lu", (unsigned long)[[[[[PodcastManager sharedManager] sessionManager] operationQueue] operations] count] );
}
}
}
But this "solution" didn't work here.
In the past, I faced a similar situation with another app and then I used blocks, but this time, I want to avoid that solution because there is some part of my current app's code I want to reuse in the future.
Anybody has faced a similar situation can bring some light?
Thank you.
Why not just run
[tableView reloadData];
after each download? Maybe I"m missing something. But that's what I'd do.
I have some code which applies to a number of objects, registering my class as the KVO:
for (SPPanelManager *manager in self.panelManagers) {
[manager addObserver:self forKeyPath:#"dataFetchComplete" options:0 context:NULL];
[manager fetchData];
}
Then when it observes a change, which happens on every of these objects, I un-register:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
//Other stuff
}
}
Then when I leave the UIViewController later, I get these errors for each of the manager objects:
An instance of class was deallocated while key value observers were
still registered with it. Observation info was leaked, and may even
become mistakenly attached to some other object.
I'm not sure why it's giving me this error - these are the only 2 places that KVO is ever referenced so it's not another observer.
Your class(observer) is being deallocated during some activity. You must unregister it before it is deallocated or not is in further use. Use code below in viewDidUnload: or dealloc:
for (SPPanelManager *manager in self.panelManagers) {
[manager removeObserver:self forKeyPath:#"dataFetchComplete" context:NULL];
}
Don't try to add or remove observers in observeValueForKeyPath:ofObject:change:context:. KVO expects the list of observers for a given Tuple(object, keyPath, context) to remain the same across a notification for that combination. Even it it works "sometimes" the behavior is non-deterministic because the order in which observers are notified is not guaranteed (it probably uses a set-type data structure internally.)
The simplest way around this problem might look something like:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
});
}
}
This will cause the observation to be removed at the next possible point in the run loop (barring custom run loop modes, which might delay its execution a bit, but usually won't be a problem). Also, you shouldn't need to worry about either self or object getting deallocated because they will be retained by the block closure until the block has executed and is, itself, releases.
As you've discovered, KVO isn't a particularly great API for one-shot notifications.
As to the initial error message, you'll need to remove the observation. You can probably get away with doing this in the observing object's dealloc but you should really avoid doing "real work" in dealloc and removing observations is arguably "real work." Unfortunately, there's not a standard teardown pattern in Cocoa, so you'd have to trigger the teardown yourself, perhaps when you segue out of the view controller, or something like that. Another answer suggested viewDidUnload but that is deprecated in iOS 6 and will never be called, so it's not a good approach any more.
You've to simply dealloc your GMS_MapView Object and as well as remove the MapView Observer forkeypath.
(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[objGMS_MapView removeObserver:self forKeyPath:#"myLocation"];
//=> Set map delegate to nil (to avoid: mapView:regionDidChangeAnimated:]: message sent to deallocated instance )
objGMS_MapView.delegate = nil;
objGMS_MapView = nil;
}
I've recently begun to discover what can be done with KVO and I'm refactoring some of my code and saving a lot of lines at the same time. I do face one issue that is so general that it makes me wonder whether a certain pattern is recommended.
In some cases I load a new view controller that needs to represent data from an already initialized model. On -viewDidLoad I would register for KVO:
[_model addObserver:self
forKeyPath:kSomeKey
options:NSKeyValueObservingOptionNew
context:(__bridge void *)(_model)];
and change my interface when values change:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:kSomeKey] && context == (__bridge void *)(_model)) {
[self updateSomeInterfaceElement];
}
Unfortunately and understandably, the view is not updated with current values from the model when I load my new view.
Is my best option to call -updateSomeInterfaceElement in -viewDidLoad? It doesn't seem to be a big deal like this, but when listening for 10-20 properties, it looks very inefficient (especially since all my -updateSomeInterfaceElement methods are mostly 1 line only, so no need to make them into a separate method). Is there any way to circumvent this, or is there a more elegant solution?
You want to change your options to include NSKeyValueObservingOptionInitial. This will cause KVO to fire a notification when you add the observer, providing the observer with the "initial" value.
Also, as an aside, you should get in the habit of calling super if observeValueForKeyPath:... is called for a notification you didn't sign up for. Also, it's a bit more bulletproof to avoid using "live" pointers in the role of KVO contexts (since a future object could have the same pointer if the current object is deallocated.) I generally prefer to use a pattern like this:
static void * const MyObservationContext = (void*)&MyObservationContext;
- (void)viewDidLoad
{
// ... other stuff ...
[self addObserver:self forKeyPath:#"model.someKey" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:MyObservationContext];
// ... other stuff ...
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == MyObservationContext)
{
// Do stuff
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}