My application performs a series of processing methods once a day or when a local notification is received to perform the series of tasks and is working as expected.
I tried adding an ActivityIndicator to a view that begins animating when a notification sent from the AppDelegate to perform the tasks is received. However all that happens is the screen darkens and none of the processing is done and the activity indicator doesn't appear. The indicator and subview that darkens the screen is used elsewhere in the app and is definitely working. I think it must be something to do with the notifications causing the app to hang and not proceed to call any of the other methods so it never completes.
This is the notification in the app delegate which is called just before the daily processing methods are called to start the activity indicator in a view controller, and the second one notifies the view controller to remove the subviews.
This method is in the AppDelegate;
- (void)GoForRankingAndSMProcessing{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ranking" object:self];
RankingAndSMProcess *process = [RankingAndSMProcess alloc];
[process DoRankingAndSocialMediaProcessing];
[process release];
[[NSNotificationCenter defaultCenter] postNotificationName:#"rankingDone" object:self];
}
Which is picked up in another view, with the notifications setup in viewDidLoad.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(workingWhileRanking:) name:#"ranking" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(workingFinishedRanking:) name:#"rankingDone" object:nil];
And those call these methods in that view.
-(void)workingWhileRanking:(NSNotification *) notification{
loading = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
loading.opaque = NO;
loading.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[indicator setHidesWhenStopped:YES];
indicator.center = self.view.center;
[indicator startAnimating];
[self.view addSubview:loading];
[self.view addSubview:indicator];
}
-(void)workingFinishedRanking:(NSNotification *) notification{
[indicator stopAnimating];
[indicator removeFromSuperview];
[indicator release];
[loading removeFromSuperview];
[loading release];
}
The notifications must be sent as the translucent subview appears, however none of the processing methods seem to be called so the view is permanently locked out until I force close the app.
Does sending the notification change the current flow of the applications launch sequence thereby ceasing to continue executing code in a class? Or have I gone about trying to add an activity indicator to show during some behind the scenes processing the complete wrong way?
If I am not interpreting incorrectly your code:
- (void)GoForRankingAndSMProcessing{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ranking" object:self];
RankingAndSMProcess *process = [RankingAndSMProcess alloc];
[process DoRankingAndSocialMediaProcessing];
[process release];
[[NSNotificationCenter defaultCenter] postNotificationName:#"rankingDone" object:self];
}
your DoRankingAndSocialMediaProcessing is the task you would like to execute while the activity indicator is showing.
I assume that all processing happens on the same thread (UI thread), and this means that when you execute DoRankingAndSocialMediaProcessing basically your run loop (and hence, the UI) blocks and it is not updated any more. This should explain your issue.
You have several options to change this behavior, depending on what you do in DoRankingAndSocialMediaProcessing.
If you do not do anything related with the UI, it would be very easy to execute that method on a background thread by replacing the direct call to DoRankingAndSocialMediaProcessing with:
[process performSelectorInBackground:#selector(DoRankingAndSocialMediaProcessing) withObject:nil];
Of course you would then send the rankingDone notification from within DoRankingAndSocialMediaProcessing to make things work correctly.
If you can afford a background thread (i.e., you are not updating the UI from within DoRankingAndSocialMediaProcessing) this is the best option, because it will not block the UI at all.
Related
I have a basic MPMoviePlayerController code that plays videos. It works flawlessly on iOS8, however it freezes the app on iOS7.
Here's the code:
- (void)playURL:(NSURL *)URL fromView:(UIView *)view
{
NSParameterAssert(URL);
NSParameterAssert(view);
NSParameterAssert(view.superview);
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:URL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieControllerDidCollapse:) name:MPMoviePlayerDidExitFullscreenNotification object:self.moviePlayer];
self.moviePlayer.shouldAutoplay = YES;
self.moviePlayer.view.frame = view.frame;
[view.superview addSubview:self.moviePlayer.view];
[self.moviePlayer setFullscreen:YES animated:YES];
}
- (void)moviePlayBackDidFinish:(NSNotification *)notification
{
[self.moviePlayer setFullscreen:NO animated:YES];
}
- (void)movieControllerDidCollapse:(NSNotification *)note
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerDidExitFullscreenNotification object:self.moviePlayer];
[self.moviePlayer.view removeFromSuperview];
self.moviePlayer = nil;
}
I have the same code running in my other apps, and it works well, but in this particular app it gets iOS7 frozen. When launched, it starts spitting numerous CGContext errors in a loop, saying that the context is 0x0. I tried to create a dummy CGContext and got rid of errors, but in this case it spins up the CPU to 100%, presumably because it is trying to draw things in the context that has a wrong scope or smth.
I also tried to use MPMoviePlayerViewController instead of MPMoviePlayerController, but it does the same thing. Modal presentation animation does not even appear.
I also searched my project for some UIAppearance setters and method swizzlings, but found nothing that could potentially cause this behavior.
I ran Time Profiler on this app, and the problem has something to do with drawing Progress Sliders. I have no progress view subclasses or categories in my project. The Instruments profiling looks like this: Instruments output. (Sorry, can't include direct image due to reputation lack).
I also tried running a clean project with the whole set of my cocoapods and it works perfectly in a different project.
OK, there problem was with one of my categories. I defined a method [UIImage imageNamed:inBundle:] which apparently conflicted with one of the private APIs. But I got no warnings what so ever. Renaming the method solved the problem.
I have a view I want to display on a certain event. My view controller is listening for a broadcast notification sent by the model and it attempts to display the view when it receives the broadcast.
However the view is not appearing. BUT if I run the exact same view code from elsewhere within the View Controller then it will be displayed. Here's some code from the VC to illustrate:
- (void) displayRequestDialog
{
MyView *view = (MyView*)[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
view.backgroundColor = [UIColor lightGrayColor];
view.center = self.view.window.center;
view.alpha = 1.0;
[self.view addSubview:view];
}
- (void) requestReceived: (NSNotification*) notification
{
[self displayRequestDialog];
}
When the above code is run the view does not appear. However if I add the call to displayRequestDialog elsewhere, for example to viewDidAppear:
- (void) viewDidAppear
{
[self displayRequestDialog];
}
Then it is displayed.
My question therefore obviously is why can I get the view to successfully appear if I call displayRequestDialog from viewDidLoad, but it will not display if called from within requestReceived?
(Note that I am not calling requestReceived prematurely before the view controller / its view has loaded and displayed)
At first I was posting the notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
Then I tried this:
NSNotification *notification = [NSNotification notificationWithName:kMyRequestReceived object:self userInfo:dictionary];
NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
[queue enqueueNotification:notification postingStyle:NSPostWhenIdle];
Then I tried this:
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
});
Then I tried this:
[self performSelectorOnMainThread:#selector(postNotificationOnMainThread:) withObject:dictionary waitUntilDone:NO];
- (void) postNotificationOnMainThread: (NSDictionary*) dict
{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dict];
}
And I have tried invoking displayRequestDialog like this:
dispatch_async(dispatch_get_main_queue(),^{
[self displayRequestDialog];
});
I have found the cause of the view not displaying - the frame's origin is getting negative values when invoked via the notification code but positive values when invoked otherwise and thus was being displayed off the screen.
No idea why there should be a difference however.
You are not listening for the notification. Do so like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(displayRequestDialog) name:kMyRequestReceived object:nil];
As far as we cannot see the code you use to register your controller to receive notifications I would recommend you to use the observer registration method which enforce getting notifications on the main thread "for free"
[[NSNotificationCenter defaultCenter] addObserverForName:#"Notification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSLog(#"Handle notification on the main thread");
}];
Within an App I make use of several viewcontrollers. On one viewcontroller an observer is initialized as follows:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
Even when removing the NSNotification before initializing the number of executions of myMethod: is being summed up by the amount of repeated views on the respective viewcontroller.
Why does this happen and how can I avoid myMethod: being called more then once.
Note: I made sure by using breakpoints that I did not made mistakes on calling postNotification multiple times.
Edit: This is how my postNotification looks like
NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:#"Number",#"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
edit: even after moving my subscribing to viewwillappear: I get the same result. myMethod: is called multiple times. (number of times i reload the viewcontroller).
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
}
edit: something seems wrong with my lifecycle. ViewDidUnload and dealloc are not getting called, however viewdiddisappear is getting called.
The way I push my Viewcontroller to the stack is as follows where parent is a tableview subclass (on clicking the row this viewcontroller is initiated:
detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];
Solution:
Moving removal of nsnotification to viewdiddisappear did the trick. Thanks for guidance!
Based on this description, a likely cause is that your viewcontrollers are over-retained and not released when you think they are. This is quite common even with ARC if things are over-retained. So, you think that you have only one instance of a given viewcontroller active, whereas you actually have several live instances, and they all listen to the notifications.
If I was in this situation, I would put a breakpoint in the viewcontroller’s dealloc method and make sure it is deallocated correctly, if that’s the intended design of your app.
In which methods did you register the observers?
Apple recommends that observers should be registered in viewWillAppear: and unregistered in viewWillDissapear:
Are you sure that you don't register the observer twice?
Ran into this issue in an application running swift. The application got the notification once when first launched. the notification increases the number of times you go into the background and come back. i.e
app launches one - add observer gets gets called once in view will appear or view did load - notification is called once
app goes into background and comes back, add observer gets called again in view will appear or view did load. notification gets called twice.
the number increases the number of times you go into background and come back.
code in view will disappear will make no difference as the view is still in the window stack and has not been removed from it.
solution:
observe application will resign active in your view controller:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)
func applicationWillResign(notification : NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
this will make sure that your view controller will remove the observer for the notification when the view goes into background.
it is quite possible you are subscribing to the notifications
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
before self gets initialized. And trying to unsubscribe 'self' which isn't really subscribed to, and you will get all global myNotification notifications.
If your view was hooked up in IB, use -awakeFromNib: as the starting point to register for notifications
It is possible that the class with the observer is, quite appropriately, instantiated multiple times. When you are debugging it will kinda look like the notification is being posted multiple times. But if you inspect self you might see that each time is for a different instance.
This could easily be the case if your app uses a tab bar and the observer is in a base class of which your view controllers are subclasses.
I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];
In my secondView I do this:
-(void)viewDidDisappear:(BOOL)animated
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dbq = [[dbqueries alloc] init];
[[NSNotificationCenter defaultCenter] postNotificationName:#"abc" object:nil];
//disabling a button and turning a progressView in the mainView on
dispatch_async(queue, ^{
//WORK
});
}
The notification disables a button and causes a progressView(in the progressView there is an ActivityIndicator) on the mainView to start.
In the area, where work is done, a method is called 3 to 6 times and if they are all done, I send another notification to the mainView to enable the button and to stop the progressView.
Now the weird thing is, that the button gets enabled at the right time but the progressView stops randomly after a few seconds
- (void)enableStartButton
{
NSLog(#"enableStartButton");
self.startARButton.enabled = YES;
[self.progressView setHidden:YES];
[[self.progressView.subviews objectAtIndex:0] stopAnimating];
}
I really don't know whats going on here, thanks in advance!