I have a method which sends out a request and has a delegate responder. The method is being called by a KVO responder. The code works fine when I call it from viewDidLoad, but when I make the request from the KVO method, it doesn't trigger the delegate method.
here is my KVO responder:
//observe app delegates userdata to check for when user data is available or data is changed
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:#"userData"] )
{
//goto facebook logic
[self FBFriendSuggester];
}
}
and the method it calls:
-(void)FBFriendSuggester
{
NSDictionary * fb_credentials = nil;
if([prefs objectForKey:#"facebookCredentials"])
{
fb_credentials = [prefs objectForKey:#"facebookCredentials"];
if ([fb_credentials objectForKey:#"expiry"] && [fb_credentials objectForKey:#"fbtoken"]) {
NSLog(#"found stored Facebook credentials.");
ApplicationDelegate.facebook.accessToken = [fb_credentials objectForKey:#"fbtoken"];
ApplicationDelegate.facebook.expirationDate = [fb_credentials objectForKey:#"expiry"];
}
}
if ([ApplicationDelegate.facebook isSessionValid]) {
printf("_valid facebook\n");
[ApplicationDelegate.facebook requestWithGraphPath:#"me/friends" andDelegate:self];
}else
printf("_invalid facebook\n");
}
and the delegate method:
- (void)request:(FBRequest *)request didLoad:(id)result{
NSLog(#"result2:%#",result);
NSDictionary * rawObject = result;
NSArray * dataArray = [rawObject objectForKey:#"data"];
for (NSDictionary * f in dataArray) {
[friends addObject:[[KNSelectorItem alloc] initWithDisplayValue:[f objectForKey:#"name"]
selectValue:[f objectForKey:#"id"]
imageUrl:[NSString stringWithFormat:#"http://graph.facebook.com/%#/picture?type=square", [f objectForKey:#"id"]]]];
}
[friends sortUsingSelector:#selector(compareByDisplayValue:)];
[self pickerPopup:self];
}
the delegate method doesn't get called in this scenario, but it does work fine when i call -(void)FBFriendSuggester directly from the controller. I don't know what to do, i tried setting a notification to respond in hopes that it would trigger the delegate, but that didn't work either.
I know this is rather old, but to give a reason for this happening, KVO notifications are sent on the thread that the change occurs on. So if a background thread makes a change to the property on the AppDelegate, the notification change will occur on the background thread as well.
So, in the context of your code, your -(void)FBFriendSuggester is most likely getting called in a background thread. You could confirm this by tossing a breakpoint in the code and looking at the Debug Navigator.
I'll continue with the assumption that this was true (It was running in a background thread).
This call, [ApplicationDelegate.facebook requestWithGraphPath:#"me/friends" andDelegate:self]; most likely needs to occur on the main thread. Reason for this is probably the underlying calls are using an NSURLConnection to make the call. The connections are made on background threads, but all delegate calls MUST be made back on the main thread, NOT a background thread. With the call andDelegate:self, its registering the object as a delegate on the background thread, instead of the main thread.
This is why your current solution fixes that issue.
Happy Coding!
I was able to work around my problem by issuing:
[self performSelectorOnMainThread:#selector(FBFriendSuggester) withObject:nil waitUntilDone:NO];
It would be great to know why this works like this though.
Related
I'm attempting to create a loading screen that draws a circle while my app makes network requests. The amount drawn of the circle is meant to represent how close the request is to finishing. However there is a serious delay (~8 seconds) between the network request and the animation. After A LOT of searching I haven't found anyone that has had this problem before so I'm very desperate.
My setup right now is that an NSProgress object will be updated as a request is being made and will trigger a KVO Notification with the NSProgress object in the userInfo. This is the same method found here.
#Client.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) {
NSProgress *progress = (NSProgress *)object;
NSDictionary *userInfo = #{#"progress":progress};
[[NSNotificationCenter defaultCenter] postNotificationName:#"FractionCompleted" object:self userInfo:userInfo];
}
}
Then the view controller that is listening for the notification will update the LoadingProgressView with the fractionCompleted of the NSProgress object that it receives.
#MainViewController.m
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateProgress:) name:#"FractionCompleted" object:nil];
...
...
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
}
Now within the LoadingProgressView, the CAShapeLayer's strokeEnd property is set as the fractionCompleted value. I am using the same idea from the tutorial found here.
#LoadingProgressView.m
- (void)updateProgress:(CGFloat)frac {
_circlePathLayer.strokeEnd = frac;
}
When I actually make a request, nothing happens until about 5 seconds AFTER the request is finished. At that point the entire circle is animated at once.
I have ABSOLUTELY NO IDEA why this is happening and it's driving me crazy. I can clearly see using the debugger that the strokeEnd property is being updated in real-time and yet the LoadingProgressView refuses to re-render until much later. Any help is very much appreciated. Thanks.
EDIT: OK so a temporary solution was to fork a new thread with a delay of 0 to update the progress view for each notification. However this seems like poor thread management since I could be creating over a hundred different threads to do the same task. I'm wondering if there's anything else I can do.
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
});
}
I ran into this same problem. It seems to be caused by setting the path from a background thread. Ensuring this happens on the main thread solved the problem. Now updating the shape draws immediately.
I have several properties in my class, I would like to call saveToFile on each property change.
I prefer not to override the setter of each property. Should I override
-[NSObject methodForSelector]? What is the best way to go?
You can register as observer to the properties you want monitored. Cocoa's KVO functionality will help you here.
Basically you need to call addObserver:forKeyPath:options:context: and register to be notified when the properties change. When this happens, the runtime calls the observeValueForKeyPath:ofObject:change:context: method on the object registered as observer. You can do here the saving you want to do.
Example for registering:
for(NSString *propName in self.propsIWantMonitored) {
[self addObserver:self forKeyPath:propName change:0 context:#selector(saveToFile)];
}
and for dealing with the change of the prop values:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// make sure we don't interfere with other observed props
// and check the context param
if (context == #selector(saveToFile)) {
[self saveToFile];
}
}
and for de-registering:
for(NSString *propName in self.propsIWantMonitored) {
[self removeObserver:self forKeyPath:propName context:#selector(saveToFile)];
}
The code samples above assume you have declared an array of properties to monitor, that you use to register as observer to. You use the context parameter to determine if observeValueForKeyPath was called as a response to the observer you just registered, or not, in order not to get into conflict with other KVO registrations made from other parts of your class.
Alternative (and more energy efficient) approach to your problem
There's one caveat with the above approach: if multiple properties are set consecutively, then the saveToFile method will be called multiple times in a short period of time, which might cause performance bottlenecks and increase the energy usage of your application.
An alternative approach would be to have a dirty flag that gets set in observeValueForKeyPath: and gets reset in saveToFile. And you can have saveToFile check the flag and don't go use the file system if the object is not dirty.
You can schedule a timer that will periodically call saveToFile, this way multiple properties set at once will result in only one disk access. You can always manually call saveToFile when you feel want an immediate save.
Note. By timer I was referring to a GCD timer, as NSTimer also has a negative energy impact on your application.
What you want is called Key-Value-Observing or KVO.
You register for example a method that gets called every time the property changes.
If you have a text field and you want to listen to changes to its text, you would register like this
[self.textField addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
And in your class you would implement this method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"text"]) {
NSLog(#"Textfield changed - MAKE CHANGES HERE");
}
}
Here's a nice tutorial, if you aren't familiar with KVO:
http://www.appcoda.com/understanding-key-value-observing-coding/
Read up on Property Observers. An example in Swift:
var currentSession: Session? {
didSet {
if let session = self.currentSession {
// Write session to file.
}
}
}
For Objective-C, key-value observing might be more proper.
I am working on a project where I there is some text in UItextview. The app wants to continuous smooth scroll that text and also wants to manage its scrolling speed. I mean here the text should scroll smoothly and the app contains slider where I can manage the speed.
Below is some sample code which I am using.
- (void) autoscrollTimerFired:(NSTimer *)timer {
[self.completeText setContentOffset:CGPointMake(0, self.completeText.contentOffset.y + 1.0) animated:NO];
if (self.completeText.contentOffset.y != self.completeText.contentSize.height - self.completeText.frame.size.height) {
scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:velocityFactor target:self selector:#selector(autoscrollTimerFired:) userInfo:nil repeats:NO];
} else {
[scrollingTimer invalidate];
}
}
The Velocity factor is the number of seconds which ranges between 0.0 to 2.5.
It works nice in simulator but in device it moves with jerks or I must say like pausing at after some line.
Could you please suggest any solution here? All suggestions are welcome.
The NSTimers actually just periodically fire events into the enclosing NSRunLoop, which each thread has (or should have). So, if you have a child (or background) process running in a different thread, the NSTimers will fire against that thread's NSRunLoop instead of the application's main NSRunLoop.
NSTimer events fire on the thread where you scheduled the timer. If you scheduled the timer on the main run loop, then the timer will fire on the main thread and be “safe and synced” with input events.
So I have some suggestions for try that (I am not sure how much it will be successful in your case)
Don't use NSTimer. Try to call from main thread via selector with "afterDelay". As given in code
[self performSelector:#selector(autoScroll) withObject:nil afterDelay:1.0];
Use KVO or may be NSNotification for event triggering. (Not do it directly ,I Think better approach.)
I prefers KVO , so writing here the steps to use it :-
Make a class with a variable of NSNumber (which should accessible outside the class). Make a object of that class and add a Observer on it. (here model is object of that class).
[model addObserver:self forKeyPath:#"name" options:(NSKeyValueObservingOptionOld |
NSKeyValueObservingOptionNew) context:nil];
Implements it's delegate methods.
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Do here what ever you want to do. It will call every time whenever there will be any change in that class variable.
}
Make a method in controller "autoScroll" which called via afterDelay selector. And change the value of the NSNumber vairabe value (any logic incremental way. Doesn't affect a lot).
Hope this helps you !!! Try This ...best of luck
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;
}
Get notification when NSOperationQueue finishes all tasks
I have the same issue as the one posted by #porneL in the post above. I tried the solution posted by #NickForge (one that received 57 votes), but I am obviously doing it wrong because it does not work for me. Here is the problem setup and my implementation:
I need to start a spinner before kicking off a set of web-service operations, and stop the spinner when they are complete. The webservices are invoked through a shared AFHTTPClient instance (part of the AFNetworking package) which adds them to its NSOperationQueue.
I set up an observer in the ViewController from which the data loads are kicked off. Did this using the answer from the above post. Implementation in my VC looks like:
In my ViewController's init method:
//add WelcomeVC as an observer for AFHTTPClient dataloadOps notifications
[[[MyCustomAFHTTPClient sharedClient] operationQueue] addObserver:self forKeyPath:#"DataLoaderEvent" options:0 context:NULL];
In my ViewController's observeValueForKeyPath method:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if( (object == [[MyCustomAFHTTPClient sharedClient] operationQueue])
&& [keyPath isEqualToString:#"DataLoaderEvent"]) {
if ( [[[MyCustomAFHTTPClient sharedClient] operationQueue] operationCount] == 0) {
NSLog(#"EUREKA!!! QUEUE IS EMPTY! DATALOAD HAS COMPLETED!");
}
}
}
The ViewController's observeValueForKeyPath method however never gets called!
Any help to get this working would be most appreciated so I can then complete implementing the spinner.
Does operationQueue have a property called DataLoaderEvent? Normally one monitors the "operationCount" property of an NSOperationQueue.
See http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html#//apple_ref/doc/uid/TP40004592
"The NSOperationQueue class is key-value coding (KVC) and key-value observing (KVO) compliant. You can observe these properties as desired to control other parts of your application. The properties you can observe include the following:
operations - read-only property
operationCount - read-only property"
try this:
[operation setCompletionBlock: ^{
NSLog(#"Finished an image.");
}];