AFNetworking Set runLoopModes? - objective-c

AFNetworking default runLoopModes is NSRunLoopCommonModes.I want use NSDefaultRunLoopMode,and set it
operation.runLoopModes = [NSSet setWithObject:NSDefaultRunLoopMode];
But it doesn't work. when I scroll the scrollView,download task still running.
Anybody can help me? thanks.

So here we go, alternative solution, without hardcoded injection. Assuming you are using UIImageView+AFNetworking extension:
// user began scrolling
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[[UIImageView af_sharedImageRequestOperationQueue] setSuspended:YES];
}
// user released finger and scrolling animation is finished
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[[UIImageView af_sharedImageRequestOperationQueue] setSuspended:NO];
}
Also check out Foursquare's solution, which relies on the same technique.

Recently I encountered similar question, while I was implementing images lazy loading in UITableView. User scrolling should have higher priority here.
Your direction is correct, the problem is in runloop modes. With UIImageView+AFNetworking extension, images are loaded with AFURLConnectionOperation, which is working on a separate network thread (see implementation), so NSURLConnections are running not on main thread.
To make the tasks stop when UITrackingRunLoopMode is active, you need to move networking code on main thread somehow.
Possible solutions:
Setup default runloop mode for your request operation.
AFImageRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:urlRequest];
[requestOperation setRunLoopModes:[NSSet setWithObject:NSDefaultRunLoopMode]];
Put connection on main thread (that's ok, NSURLConnections are running their own threads): Find + (NSThread *)networkRequestThread and make it return [NSThread mainThread]. That might be not suitable with other situations, beware locks.
I am still not sure why AFNetworking is creating it's separate network thread. To process incoming data in background? If anybody have a guess, please reply.
Also see this question.

Related

Problem with NSProgressIndictor on mojave

I implemented NSProgressIndicator on sierra os and it worked fine in the application on it.However, for testing when i moved the application to high-sierra or mojave, theprogressbaris not displayed at all, while it works fine on sierra
when clicking the button in the application, inside the button function, i initiated the progressbar as follow,
[_progressbar startAnimation:sender];
[_progressbar setHidden:false];
and when i return i change setHidden to true. So, where am i possibly going wrong?
UPDATE:
I tried to use the following code,
//Create the block that we wish to run on a different thread.
void (^progressBlock)(void);
progressBlock = ^{
[_progressbar setDoubleValue:0.0];
[_progressbar startAnimation:sender];
[_progressbar setHidden:false];
// running = YES; // this is a instance variable
//NOTE: It is important to let all UI updates occur on the main thread,
//so we put the following UI updates on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[_progressbar setNeedsDisplay:YES];
});
// Do some more hard work here...
}; //end of progressBlock
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
I noticed this problem on my old macOS apps, and if I recall correctly it was after first testing in High Sierra betas, as you just found.
Most user interface views need the main thread to draw or receive user input. But it seems that, in earlier macOS versions, Apple had done some magic to make NSProgressIndicator display and animate even if the main thread was busy doing other work. Then in recent macOS versions (10.13?), this magic seems to have been removed.
It never was a good idea to do long-running work on the main thread, since it makes other views in your user interface, which never had the NSProgressIndicator magic, unresponsive to the user. I think Apple figured that since we're all multi-threaders now with Dispatch (also called Grand Central Dispatch), it was time to remove the magic, which was probably difficult for them to maintain. I'd always found it to be somewhat buggy and unreliable.
So the answer is that you need to get whatever work that progress indicator is tracking off of the main thread. You will probably be using dispatch_async() and friends, although NSOperation and NSOperationQueue still work and will also do the job.

dispatch_async doesn't work as advertised

I'm still pretty green with iOS, but the code below seems like it should work. I have a bunch of work to do in the background so a "busy indicator" is supposed to be shown to the user while the work is done. But what happens is the busy indicator does not appear for several seconds. It only appears once most of the work (but not all) has completed.
- (void) fetchDataTest {
// next line will create and display a busy indicator
__block MRProgressOverlayView *overlay = [MRProgressOverlayView showOverlayAddedTo:self.view title:#"Loading…" mode:MRProgressOverlayViewModeIndeterminate animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(void) {
[self fetchData];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[overlay hide:YES];
});
});
}
I have lots of logging (not shown above) that runs so slowly I can watch the output log (including thread ids) and see overlay has been created and background work started - yet no overlay on my device appears for several seconds.
How can I get the overlay to appear immediately?
Observations:
you shouldn't use GCD to make synchronous data fetches look asynchronous — just use the asynchronous fetches directly. They're much better for overall scheduling and for battery life;
it's the bit that occurs before the dispatch_asyncs that isn't happening, not the stuff within them;
Jack Wu's guess is probably right: on what thread or queue is fetchDataTest being performed? It should be the main queue.
On the last point: UIKit is not generally thread safe. With a few exceptions, you should always use it from the main queue. That's a large part of what the main queue is for nowadays: all user interaction. Then heavy work should be passed off elsewhere.

NSRunAlertPanel caused performance issue on multithreading

Occasionally I have to do a popup alert window in my Cocoa code segments. Previously I used NSAlert directly then have runModal to go while I found that the NSRunAlertPanel is more easier to achieve my goal. So I decided to switch all my alert functions to NSRunAlertPanel. It seemed okay at most time。
Now I'm adding multithreading. I found that NSRunAlertPanel appears clearly slower than NSAlert when calling back in the main thread.
Code segments:
Firstly I create a thread:
[NSThread detachNewThreadSelector: #selector(tryRunLoop:) toTarget:self withObject:nil];
Then this functiontryRunLoop in this thread call the alert window function in the main thread:
while(1)
[self performSelectorOnMainThread:#selector(showAlert:) withObject:anObject waitUntilDone:YES];
The function showAlert in main thread do the rest things:
NSRunAlertPanel(#"Warning:",#"Just testing", #"YES", nil, nil);
As time goes by the response of the popup window appears slower and slower.If I use NSAlert instead of NSRunAlertPanel, or did not run the popup method in main thread, the symptom should disappear.
I also found that the CPU usage was also different between these two methods. Obviously NSAlert costs low CPU usage while hitting the button all the time.
Is someone able to explain these phenomenons?
PS: I was not allowed to put the whole original project online so that I've created a simple Cocoa project in Github to simulate the symptom and the URL ,please take a look at the Known issues in Readme file at first.
Alright, the short answer is don't use NSRunAlertPanel. That family of functions have been discouraged for some time now, and superseded by NSAlert. Use NSAlert instead.
(Unfortunately the class reference for NSRunAlertPanel etc. doesn't mention this; I'm trying to remember where it was first documented; perhaps a release note)

uiprogressview update issue

I have two views, first one "A", has a button that trigger a web service to validate one code, if validation is successful, it performs a segue to the view "B".
In view B, I have this
- (bool) viewDidUpload
dispatch_async(kBgQueue, ^{
[self processContacts];
[self sendPost];
});
-(void) processContacts
//Process contact address
-(void) sendPost
//Process web services call
//If web service call is OK, call [self updateBar]
[self performSelectorOnMainThread:#selector(updateBar)
withObject:nil waitUntilDone:YES];
UPDATED: I implemented GDC to send sendPost and processContacts in the background, then sendPost method has the updateBar call performed using performSelectorOnMainThread:, the problem is that the execution doesn't enter in this method and it finishes without updating the bar. sendPost and processContacts now run in the background, but I don't know how to perform updateBar in the main every time a valid connection is done, and then return to the background and perform rest of connections and again.
Many thanks
You should perform all calculations and communications in background thread. Then do updates on GUI stuff (like progress bar) in your main thread.
Take a look at GCD. It is really easy to use and will solve your problem I'm sure.
So seems that you are using internet and best way for me to do so is using the Git library of ASIHTTPRequest, read it and download it then use it, it's very useful, enjoy:
http://allseeing-i.com/ASIHTTPRequest/

Best way to wait app Launch

I have an app that requests from the server some data on xml type. Ok, it does ok.
But seems this action loads a little the app on launching.
TBXML *tbxml = nil;
tbxml = [[TBXML tbxmlWithURL:[NSURLURLWithString:#"http://www.someplace.com/test/test.xml"]] retain];
So, what i figured is make the app wait to launch completely to do this action. So, i've search for it, and find two ways.
applicationDidFinishLaunching;
awakeFromNib;
I dont know if this is the correct way to do this. So, im open to suggestions.
Thanks!
First, I would suggest you to put that XML loading code in a separate thread using NSOperationQueue or NSThread so that it won't block the main thread.
applicationDidFinishLaunching, IMHO, should be used to initialize your RootViewController, handle incoming push notifications, local notifications, etc. Use viewDidLoad in RootViewController for your purpose.