I have created a project on GitHub so I can learn how to optimize networking for my iOS apps. I have used blocks and GCD heavily and after watching WWDC 2012 videos and videos from past years I learned that I may be able to do more with NSOperationQueue. Specifically I can control the number of concurrent operations (network connections) as well as provide cancellation of operations. I am experimenting with allowing 1, 2, 4, 8 and 16 concurrent operations and I am seeing interesting results that I did not totally expect. I am measuring the results but I wonder if there is more that I should be measuring.
You can find sample project here:
https://github.com/brennanMKE/OptimizedNetworking
Since I am using the async API of NSURLConnection there is plenty of benefit to having many concurrent connections because the API spends a fair amount of time waiting for HTTP packets. Previously my code would start with an array of items to download and request them all sequentially, which is prevents the benefits of concurrency. I have also been using notifications to cancel network connections. Now I can do that with this project through operations and I have set them up to use a value for priority and a category so that I can prioritize and sort downloads and cancel a category of operations. I may choose to use a category for each view and when a user leaves a view all operations for that view will be cancelled using the category. This will free up resources for the active view.
One concern with using more concurrent operations is CPU usage as well as I/O, but I am not aware of a way to measure these values with iOS. The equivalent of the "w" command in iOS to show CPU usage could be useful. I am less concerned about I/O but measuring it would be more comprehensive.
My main issue with how I was doing networking was a responsive UI. I found that what I have been doing has made the UI sluggish. This new approach may help a great deal, but only if I keep the number of concurrent operations down. The optimal number of operations may vary by the type of connection (3G, WiFi, etc) so checking the connection type may lead to some optimizations.
If you are interested in better ways to speed up network communications in your app please try out this sample project and suggest other ways that I can measure performance and offer ways to further optimize communications. (Also note that I am referencing the Apple sample project MVCNetworking as well as the ASIHTTPRequest project.
What I may do next is to total up the amount of data downloaded and keep a log of that amount along with the total time to complete the download.
The README file should help explain the project and how it works.
If this helps Mugunth Kumar actually checks the type of connection using the reachability class before setting the NSOperationQueue max connection size in the MKNetworkKit
Related
I was going through this excellent blog post
(http://www.humancode.us/2014/08/14/target-queues.html)
of target threads in iOS and I could not help but wonder why do we need such a mechanism. In the example, we are specifying a serialised target queue for a custom concurrent queue. Can we not achieve the same by executing the blocks in the original concurrent queue in a serialised queue instead?
Whats the point of having a serialised target queue for a concurrent queue????
If I got You right, you're asking why would someone start serial task on a concurrent queue.
You would need that kind of behaviour in case, if most tasks with some resource can be performed concurrently (aka, simultaneously), but some tasks are, by nature, unsafe to be performed concurrently with others.
The most common example is readers/writers problem. Here you are accessing, for example, some resource of a file system. It's ok to read it even from different threads - every reader will get exactly what it needs. But here comes necessity to update contents of that file. Modifying it while someone reads it leads to unpredicted results - reader is not guaranteed to get the right, expected, info (partially from old version, partially from new). Even worse - there can be two writers (if file contents changes by application user and from some central storage via net) - result will be some crazy mix of two versions (actually, it can be now even corrupted)
Here comes necessity for each writer to wait till all other tasks performed (no one reads, no one writes), and for each reader to wait until no writing tasks take place (no one writes, no matter how many readers)
Wikipedia has nice article on this one. I haven't run into any other practical situations, where you would need this, but I believe there're more of them.
Hope it answers your question
this is an open question aiming at understanding what is the best practice or the most common solution for a problem that I think might be common.
Let's say I have a list of URLs to download; the list is itself hosted on a server, so I start a NSURLConnection that downloads it. The code in connectionDidFinishLoading will use the list of URLs to instantiate one new NSURLConnection, asynchronously, per each URL; this in turn will trigger even more NSURLConnections, and so on - until there are no more URLs. See it as a tree of connections.
What is the best way to detect when all connections have finished?
I'm aiming the question to iOS7, but comments about other versions are welcome.
A couple of thoughts:
In terms of triggering the subsequent downloads after you retrieve the list from the server, just put the logic to perform those subsequent downloads inside the completion handler block (or completion delegate method) of the first request.
In terms of downloading a bunch of files, if targeting iOS 7 and later, you might consider using NSURLSession instead of NSURLConnection.
First, the downloading of files with a nice modest memory footprint is enabled by initiating "download" tasks (rather than "data" tasks).
Second, you can do the downloads using a background NSURLSessionConfiguration, which will let the downloads continue even if the user leaves the app. See the Downloading Content in the Background section of the App Programming Guide for iOS. There are a lot of i's that need dotting and t's that need crossing if you do this, but it's a great feature to consider implementing.
See WWDC 2013 What's New in Foundation Networking for an introduction to NSURLSession. Or see the relevent chapter of the URL Loading System Programming Guide.
In terms of keeping track of whether you're done, as Wain suggests, you can just keep track of the number of requests issued and the number of requests completed/failed, and in your "task completion" logic, just compare these two numbers, and initiate the "all done" logic if the number of completions matches the number of requests. There are a bunch of ways of doing this, somewhat dependent upon the details of your implementation, but hopefully this illustrates the basic idea.
Instead of using GCD you should consider using NSOperationQueue. You should also limit the number of concurrent operations, certainly on mobile devices, to perhaps 4 so you don't flood the network with requests.
Now, the number of operations on the queue is the remaining count. You can add a block to the end of each operation to check the queue count and execute any completion logic.
As Rob says in his answer you might want to consider NSURLSession rather than doing this yourself. It has a number of advantages.
Other options are building your own download manager class, or using a ready-made third party framework like AFNetworking. I've only worked with AFNetworking a little bit but from what I've seen its elegant, powerful, and easy to use.
Our company wrote an async download manager class based on NSURLConnection for a project that predates both AFNetworking and NSURLSession. It's not that hard, but it isn't as flexible as either NSURLSession or AFNetworking.
I have an NSArray of links. I want to parse through them with an online article extractor API (Clear Read), and with the result given back for each article (some HTML) I throw it into an NSString.
My problem arises from the fact that, say my array has 100 URLs in it, I loop through the array shooting each item into the API and getting back some results in JSON. This is firing like 100 NSURLConnection calls at once asynchronously.
I wasn't sure if that'd be a problem, but when I give it 100 URLs (real strings, none are nil) the data that comes back often has either empty values for the JSON keys (when they shouldn't), or the data coming back is nil. There's also a bunch of duplicates.
Should I be handling multiple asynchronous connections better than I am now? If so, how?
A couple of thoughts:
If you're doing concurrent asynchronous requests and are using asynchronous NSURLConnection, then you'll want to define your own class for this download operation to make sure that every connection keeps track of its own properties. That way, everything can be encapsulated within this class where the resulting download objects can keep track of what's downloaded, what's been parsed, etc. If you're not using asynchronous NSURLConnection (e.g. you're just using dataWithContentsOfURL), it's even easier, though you lose some of the progress updates that NSURLConnection provides and/or streaming opportunities.
For best performance, you should do concurrent requests. Having said that, you should not have more than four or five concurrent requests going to any particular server. This is an iOS imposed constraint, and especially if you have a slow network connection, you risk having connections timeout otherwise.
If you're doing preliminary testing on the simulator, you may want to make sure you try out the "network link conditioner". It's part of the "Hardware IO Tools for Xcode", available at the Downloads for Apple Developers. There are issues (such as the aforementioned timeout problems if you have too many concurrent requests going to a particular server) that only manifest themselves in slow connections.
Having said that, you also want to make sure to test your solution on a device with real world network speeds. It's easy to successfully run massively parallel tasks successfully on the simulator that are too greedy for the device. Limiting the number of concurrent sessions to five will diminish this resource problem, but it should be part of your testing strategy.
I agree with JRG-Developer, that you should look into established frameworks, such as AFNetworking. Make sure to set the maxConcurrentOperationCount for the queue of the AFHTTPClient, though, if queueing 100 plus operations.
I don't know how much data your 100 requests entail, but be forewarned that the app approval process has been known to reject apps that make extraordinary networks requests on cellular networks. What constitutes excessive cellular network activity is not explicitly stated in the app review guidelines, though Avoiding iPhone App Rejection From Apple has claimed that you should ensure that you don't exceed more than 4.5mb in 5 minutes. You can use Reachability to determine what type of network you are on and perhaps warn the user if they're on cellular (if the amount of data approaches this threshold).
Have you considered using a third party framework - such as AFNetworking - and limiting the number of asynchronous calls happening at once? Perhaps this might help / solve your problem.
In particular, you might consider creating a networking manager class that creates and manages AFHTTPClient(s), which in turn manages AFHTTPRequestOperations, for each endpoint (base URL) you hit.
I have an iOS app which synchronizes a certain number of assets at startup. I'm using AFNetworking and set up an NSOperationQueue to handle all of the downloads. I was wondering, how many simultaneous downloads make sense. Is there a limit where network performance will drop if I have to many at the same time? At the moment I'm doing max 5 downloads at a time.
This depends on several factors:
What is the network speed and latency?
What is the data size of the requests and responses?
How long does processing a request take on the server?
How long does processing a response take on the client?
How many parallel requests can the server fulfill efficiently?
How many users will make requests at the same time?
What is the minimal speed and memory size of the target device?
For small and medium sized applications, the limiting factor is usually the device's network latency, but that might not be the case in your situation. In the end, you'll have to test and figure out the most efficient compromise. 5 is a good number to start with.
You might want to set the number of concurrent downloads by the available network connection (WLAN or 3G or even slower...).
The beauty of using NSOperationQueues is that they are closely tied into the underlying OS (iOS or OSX). The queue decides how many operations to run based on many factors, including free memory, load on the system, etc.
You should not try to second guess the system and throttle yourself. Queue as many operations as you have and let the OS deal with it. I have an iPhone app that adds hundreds of operations in the queue when it has to fetch images of varying sizes etc. Works great, UI is not blocked, etc.
EDIT: well, it seems that when doing NSURLConnections and similar network connections, NSOperationQueue is NOT really keyed in to network usage. I asked on the Apple internal forums this summer, and in the end was told by Quinn "The Eskimo" (Apple network guru) to use a limit of something like 4. So this post is correct in the sense of pure processing power - NSOperationQueue will do the right thing - but when it comes to network ops you need to set a limit.
Depends on your hardware mostly I would say. Best way to address this is to test it with multiple cases with multiple trials. Try to diversify the hardware you test on as much as possible (remember do not use the simulator to test this!).
There actually is a constant the SDK provides that varies depending on various constraints. I would recommend you look into using it.
Regarding this question, I've done some tests on a Ipad2 IOS6.0. I've created a little app that performs an HTTP-GET request to a webserver. This webserver will provide data for 60 seconds ( this to get a meaningfull result, will change this to 10 min later in my tests ).
For one HTTP-GET request it works very good. Then I tried to perform several HTTP-request at the same time and see how many and how fast I can download over a WIFI connection of the IPad
I made 2 versions. 1 version using NSOperations and 1 version using NSThread an Synchron HTTP-GET request. In short, I always get a TimeOut for my 6th request. ( The tcp-syn doesn't get to my HTTP-Server ).
Extra info:
NSThead-implementation:
Simply make a for loop and create a Thread. This will perform a synchronized HTTP requests.
There I observe that my 6th request times out after 20 seconds. If I set the Timeout to 80 seconds, I clearly see that after the end of my first http-request ( after 60 seconds ) my 6th request is launched...
NSOperation-implementation:
Create a Queue and set the maxConcurrentOperations to 12. Add 12 http-request Operations to the queue. Here as well I notice that the 6th request gets a -1001 error code ( meaning: timout ). and I see no tcp-syn of the 6th request.
I'm familiar with using Reachability to determine the type of internet connection (if any) being used on an iOS device. Unfortunately that's not a decent indicator of connection quality. Wifi with low signal strength is pretty sketchy and 3G with anything less than 3 bars is a disaster (not to mention networks that only allow EDGE connections).
How can I determine the quality of my connection so I can help my users decide if they should be downloading larger files on their current connection?
A pragmatic approach would be to download one moderately large-sized file hosted on a reliable, worldwide CDN, at the start of your application. You know the filesize beforehand, you just have to measure the time it takes, make a simple computation and then you've got your estimate of the quality of the connection.
For example, jQuery UI source code, unminified, gzipped weighs roughly 90kB. Downloading it from http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.js takes 327ms here on my Mac. So one can assume I have at least a decent connection that can handle approximately 300kB/s (and in fact, it can handle much more).
The trick is to find the good balance between the original file size and the latency of the network, as the full download speed is never reached on a small file like this. On the other hand, downloading 1MB right after launching your application will surely penalize most of your users, even if it will allow you to measure more precisely the speed of the connection.
Cyrille's answer is a good pragmatic answer, but is not really in the end a great solution in the mobile context for these reasons:
It involves doing a test "at the start of your application" by which I assume he means when your app launches. But your app may execute for a long while, may go background and then back into the foreground, and all the while the user is changing network contexts with changes in underlying network performance - so that initial test result may bear no relationship to the "current" performance of the network connection.
For the reason he rightly points out, that it is "penalizing" your user by making them download a test file over what may already be constrained network conditions.
You also suggest in your original post that you want your user to decide if they should download based on information you present to them. But I would suggest that this is not a good way to approach interacting with mobile users - that you should not be asking them to make complicated decisions. If absolutely necessary, only ask if they want to download the file if you think it may present a problem, but keep it that simple - "Do you want to download XYZ file (100 MB)?" I personally would even avoid even that.
Instead of downloading a test file, the better solution is to monitor and adapt. Measure the performance of the connection as you go along, keep track of the "freshness" of that information you have about how well the connection is performing, and only present your user with a decision to make if based on the on-going performance of the connection it seems necessary.
EDIT: For example, if you determine a patience threshold that in your opinion represents tolerable download performance, keep track of each download that the user does in order to determine if that threshold is being reached. That way, instead of clogging up the users connection with test downloads, you're using the real world activity as the determining factor for "quality of the connection", which is ultimately about the end-user experience of the quality of the connection. If you decide to provide the user with the ability to cancel downloads, then you have an excellent "input" about the user's actual patience threshold, and can adapt your functionality to that situation, by subsequently giving them the choice before they start the download. If you've flipped into this type of "confirmation" mode, but then find that files are starting to download faster, you could dynamically exit the confirmation mode.
Rob's answer is very good, but for a more specific implementation start with (https://developer.apple.com/library/archive/samplecode/SimplePing/Introduction/Intro.html#//apple_ref/doc/uid/DTS10000716)Apple's Simple Ping example source code
Target the domain for the server that you want to monitor connection quality to. Use the ping library to "ping" it on a regular basis (say 1 or 10 seconds depending upon your UI needs). Measure how long it takes to get a response to your ping (or if it never returns) to develop an estimate of the connection quality to communicate to your user.