In my app I have pretty long
- (void)appDidEnterBackground:(NSNotification*)notif method,
it takes 1-2 seconds to execute. This causes the following issue: if I close app and open it again very fast, than
- (void)appWillEnterForeground:(NSNotification*)notif
is being called before -appDidEnterBackground is finished, which leads to crash - data is not consistent, or something like this. Rather than investigate what exactly is wrong in my data, I want to prevent this case from ever happening - I want to wait until appDidEnterBackground is done.
My code:
- (void)appDidEnterBackground:(NSNotification*)notif
{
[self processAppDidEnterBackgroundRoutines];
NSLog(#"%s - end", __FUNCTION__);
}
- (void)processAppDidEnterBackgroundRoutines
{
// 1-2 seconds prosessing
}
- (void)appWillEnterForeground:(NSNotification*)notif
{
NSLog(#"%s - begin", __FUNCTION__);
}
I tried to call
[self performSelectorOnMainThread:#selector(processAppDidEnterBackgroundRoutines) withObject:nil waitUntilDone:YES];
, but it doesn't help for some reason - appWillEnterForeground: is still being called before processAppDidEnterBackgroundRoutines is finished.
Does anyone have others ideas how to synchronise this calls?
Would it work for you to put both in a serial queue?
- (void)appDidEnterBackground:(NSNotification*)notif
{
dispatch_sync( serialQueue, ^()
{
[self processAppDidEnterBackgroundRoutines];
});
}
- (void)processAppDidEnterBackgroundRoutines
{
// 1-2 seconds prosessing
}
- (void)appWillEnterForeground:(NSNotification*)notif
{
dispatch_sync(serialQueue, ^()
{
// do your stuff
});
}
Your problem appears to be because you're using performSelectorOnMainThread: in your appDidEnterBackground: method. This is causing that selector to be run later on, because you're already running on the main thread. Just stop doing performSelectorOnMainThread:, because it's unnecessary, and is what's causing your problem.
Related
I'm building a "monitoring" app on my iPhone. I'm using AFNetworking-2.0. I have a backend server exposing a RESTful interface written in Python3/tornado.
Depending on what level of ViewController I'm at, I want to poll different data with different queries (the focus of the application tunes the focus of the queries). In the interest of "Make it Work", I have set up the following:
#pragma mark - Pull Loop
- (void) forkPull {
NSString* uri = [NSString stringWithFormat: #"%#/valves", Site.current.serialID];
[[HttpConnection current]
GET: uri
parameters: #{}
success:^(NSURLSessionDataTask* task, id responseObject){
[Site.current performSelectorOnMainThread: #selector(fromDoc:) withObject:responseObject waitUntilDone:YES];
NSTimeInterval delay = 60; // default poll period
// attempt to hone in if we have valid lastTouch info
if (Site.current.touched != nil) {
NSDate *futureTick = [Site.current.touched dateByAddingTimeInterval: 65];
if ([futureTick compare: [NSDate date]] == NSOrderedDescending) {
delay = futureTick.timeIntervalSinceNow;
}
}
[self performSelector: #selector(forkPull) withObject:nil afterDelay:delay];
NSLog(#"%# forkPull again in %f", self, delay);
}
failure:^(NSURLSessionDataTask* task, NSError* error){
NSLog(#"%# forkPull error: %# (uri=%#)", self, error, uri);
[self performSelector: #selector(forkPull) withObject:nil afterDelay:60];
}
];
}
- (void) stopPull {
[NSObject cancelPreviousPerformRequestsWithTarget: self];
}
#pragma mark - View Management
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear: animated];
....
[self forkPull]; // start up polling while I'm visible
}
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self stopPull]; // I'm going away, so shut down the pull loop?
...
}
Basically, when the controller's view appears, it sends a REST query (when it gets back asynchronously, it will update the model in the fromDoc: methods; the controller has KVO relationships set up which will cause UI changes. After the update completes, it's able to approximate about when it should make the next pull, and schedules that with performSelector:withObject:afterDelay:. When another controller takes center stage, the viewWillDisappear: method attempts to stop any forkPulls that have been queued.
While this kinda works. I'm pretty sure it doesn't pass the "Make it Right" test. I'm naive about how all the tasks and backgrounding work, but it seems to me that AFNetworking adds its own level of them, so my stopPull might not be effective. I've seen some evidence of that with my NSLog output, where it seems that controllers that aren't on the top anymore, still have loops running.
But I'm sure others have done this kind of pattern before. I'd love to know how to better architect/implement this. I'm looking for someone to share the pattern they've used for doing the semi-periodic REST queries, that has been vetted and works well.
Use Grand Central Dispatch:
#property (strong, nonatomic) dispatch_source_t timer;
- (void)startTimer
{
if (!self.timer) {
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
}
if (self.timer) {
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 60ull*NSEC_PER_SEC, 10ull*NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^(void) {
[self tick];
});
dispatch_resume(_timer);
}
}
- (void)tick
{
// Do your REST query here
}
This will invoke your tick method every 60 seconds.
To suspend and resume your timer, use dispatch_suspend and dispatch_resume:
dispatch_suspend(self.timer);
dispatch_resume(self.timer);
You can invoke dispatch_source_set_timer at any time later to schedule ticks sooner or delay them until later:
// Fire sooner than 60 seconds, but resume 60s fires after that
unsigned long long delaySeconds = arc4random() % 60;
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, delaySeconds * NSEC_PER_SEC), 60ull*NSEC_PER_SEC, 10ull*NSEC_PER_SEC);
See the Apple Concurrency Programming Guide for full docs on this.
I'm having this wierd problem with the app freezing at a certain point. I'm guessing its got to do with how I'm using NSConditionLock.
Theres a library I have been given to use, which consists of a series of survey questions, but it works in such a way that it races directly to the last question without accepting answers, hence the need to pause the thread and accept input from the user.
I haven't used it before so maybe someone could help if I'm implementing it wrongly?
Please let me know if the code provided is insufficient.
- (void)viewDidLoad
{
[super viewDidLoad];
//INITIALISE CONDITION LOCK WITH CONDITION 0
condition=[[NSConditionLock alloc]initWithCondition: 0];
}
- (IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
}
else
{
//DO SOME STUFF AND THEN UNLOCK
[condition unlockWithCondition:1];
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition lockWhenCondition: 1];
}
EDIT: In short, I want the Objc equivalent of this java snippet...
this.runOnUiThread(showUI);
try
{
//SLEEP
Thread.sleep(1000*60*60*24*365*10);
}
catch (InterruptedException e)
{
//WAKE
setResponse(at,showUI);
}
EDIT 2: ShowUI method on Paul's request.
[self removePreviousSubViews];
switch ([task getType]) {
case SingleChoiceType:
{
NSLog(#"SingleChoiceType");
isMultipleChoice = NO;
[self addSingleChoiceView:nil];
break;
}
case TextType:
{
NSLog(#"TextType");
self.txtTextType.keyboardType=UIKeyboardTypeDefault;
[self addTextTypeView:nil];
break;
}
...more cases
}
-(void)addTextTypeView:(NSSet *)objects
{
self.txtTextType.text = #"";
CGRect frame = self.txtQuestionType.frame;
// frame.size = [self.txtQuestionType sizeThatFits: CGSizeMake(self.txtQuestionType.frame.size.width, FLT_MAX)];
frame.size.height = [self textViewHeightForAttributedText:self.txtQuestionType.text andWidth:self.txtQuestionType.frame.size.width andTextView:self.txtQuestionType];
self.txtQuestionType.frame=frame;
self.textTypeView.frame = CGRectMake((self.view.frame.size.width - self.textTypeView.frame.size.width)/2, ( self.txtQuestionType.frame.origin.y+self.txtQuestionType.frame.size.height), self.textTypeView.frame.size.width, self.textTypeView.frame.size.height);
[self.view addSubview: self.textTypeView];
}
I agree with BryanChen, I think you may have another issue. Without details on the survey library, it is impossible to confirm, but assuming that it is a UIViewController than accepts touch inputs to progress through a series of questions, it is hard to see why it is a threading issue - it simply shouldn't advance without user interaction.
That aside, your use of NSCondtionLock doesn't look right either.
Essentially an NSConditionLock has an NSInteger that represents the current 'condition', but just think of it of a number. There are then two basic operations you can perform -
lockWhenCondition:x will block the current thread until the 'condition' is 'x' and the lock is available. It will then claim the lock.
unlockWithCondition:y releases the lock and sets the condition to 'y'
There are also methods to set timeouts (lockBeforeDate) and try to claim the lock without blocking (tryLock, tryLockWhenCondition).
To synchronise two threads, the general pattern is
Initialise Lock to condition 'x'
Thread 1 lockWhenCondition:x -This thread can claim the lock because it is x
Thread 2 lockWhenCondition:y - This thread will block because the lock is x
Thread 1 completes work, unlockWithCondition:y - This will enable Thread 2 to claim the lock and unblock that thread
Your code looks strange, because you are starting a thread in your if clause but unlocking in an else clause. I would have thought you would have something like -
-(IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
[condition:lockWithCondition:1]; // This will block until survey thread completes
[condition:unlockWithCondition:0]; // Unlock and ready for next time
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[condition lockWhenCondition: 0];
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition unlockWithCondition:1];
}
BUT This looks like a recipe for deadlock to me, because you are performing the showUI selector on the main thread that is blocked waiting for the survey thread to complete.
Which brings us back to the question, what does showUI do and why is it skipping directly to the end?
I've gotten in a few cases when something receives multiple refresh calls in quick succession, eg:
- ViewController receives multiple KVO notifications.
- Datamanger class that is called from setters to refresh when multiple settings change.
Ideally I would like to execute only the last refresh call from a series (drop all the intermediate ones).
Right now I'm using an isRefreshing property and a needRefresh to block excessive refreshes, eg:
- (id)init {
...
[self observeValueForKeyPath:#"isRefreshing" ....];
}
- (void)setParameter:(NSInteger)parameter {
....
[self refresh];
}
/* and many more kinds of updates require a refresh */
- (void)setAnotherProperty:(NSArray*)array {
....
[self refresh];
}
- (void)refresh {
if (self.isRefreshing) {
self.needRefresh = YES;
return;
}
self.isRefreshing = YES;
...
self.isRefreshing = NO;
}
- observeValueForKeyPath..... {
if (!self.isRefreshing && self.needsRefresh) {
self.needsRefresh = NO;
[self refresh];
}
}
Is there a better solution for this kind of problem?
You can create a NSOperationQueue with concurrency set to one and only submit a new operation to it when its operation count is zero. (Or use cancellation logic to remove pending jobs so that only one new one is queued if there's a job in progress.)
What you're doing is reasonable for a single-threaded system but would become fairly complicated for multiple threads.
Looks like you should delay refreshing for a while.
You can use different techniques to do so. It is enough only one flag.
For example you may use async block to make a delay for a one main run-loop cycle
- (void)setParameter:(NSInteger)parameter {
....
[self requestRefrhesh];
}
- (void)setAnotherProperty:(NSArray*)array {
....
[self requestRefrhesh];
}
...
-(void) requestRefrhesh {
if (self.refreshRequested) {
return;
} else {
self.refreshRequested = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run in main UI thread
//make your UI changes here
self.refreshRequested = NO;
});
}
}
I've got class:
ClassX.m
#property (assign) BOOL wasProcessed;
-(void) methodA { //<- this can be called many times in short period of time
dispatch_async(dispatch_get_main_queue(), ^{
[self methodB];
});
}
- (void) methodB {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
Since dispatch_async is used so a few calls to methodB can be processed concurrently at the same time and following code needs to be atomic:
if (!self.wasProcessed) {
self.wasProcessed = YES; //e.g two calls can enter here before setting YES and it would be bad because I want to process it only one time
How can those 2 lines be made atomic (checking and setting variable)? I dont want to make atomic code that is after "self.wasProcessed = YES;" so moving whole if to #synchronize(self) won't be good solution. If there is anything wrong with my thinking please point it out as I'm not very experienced in those topics, Thank you.
Try #synchronized. While the enclosed code is being executed on a thread, it will block other threads from executing it.
- (void) methodB {
#synchronized(self) {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
}
-(void) methodA {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^(){
[self methodB];
}];
});
}
Your's methodB will be only called in main thread, so it will be never performed simultaneously.
In Java you can suspend the current thread's execution for an amount of time using Thread.sleep(). Is there something like this in Objective-C?
Yes, there's +[NSThread sleepForTimeInterval:]
(Just so you know for future questions, Objective-C is the language itself; the library of objects (one of them at least) is Cocoa.)
Sleeping for one second in Java:
Thread.sleep(1000);
Sleeping for one second in Objective C:
[NSThread sleepForTimeInterval:1.0f];
Why are you sleeping? When you sleep, you are blocking the UI and also any background URL loading not in other threads (using the NSURL asynchronous methods still operates on the current thread).
Chances are what you really want is performSelector:withObject:AfterDelay. That's a method on NSObject you can use to call a method at some pre-determined interval later - it schedules a call that will be performed at a later time, but all of the other stuff the thread handles (like UI and data loads) will still continue.
Of course, you could also use the standard Unix sleep() and usleep() calls, too. (If writing Cocoa, I'd stay with the [NSThread sleepForTimeInterval:], however.)
If you use NSThread sleepForTimeInterval(commented code) to sleep, fetching data will be blocked, but +[NSThread sleepForTimeInterval:] (checkLoad method) will not block fetching data.
My example code as below:
- (void)viewDidAppear:(BOOL)animated
{
//....
//show loader view
[HUD showUIBlockingIndicatorWithText:#"Fetching JSON data"];
// while (_loans == nil || _loans.count == 0)
// {
// [NSThread sleepForTimeInterval:1.0f];
// [self reloadLoansFormApi];
// NSLog(#"sleep ");
// }
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
}
-(void) checkLoad
{
[self reloadLoansFormApi];
if (_loans == nil || _loans.count == 0)
{
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
} else
{
NSLog(#"size %d", _loans.count);
[self.tableView reloadData];
//hide the loader view
[HUD hideUIBlockingIndicator];
}
}
usleep() can also be used as ive used this to pause the current thread at times