1

I am doing a UITableview to download data the name webservice is very fast, so I use it to populate the table initially, then I start an operation queue for the image.

Then a seperate queue for the rest of data because it loads very slow but that effects the image load time, How can I do the 2 concurrently.

Can you figure out whats slowing the performance there and help me fix it?

real 19
  • 748
  • 8
  • 32

2 Answers2

1

As I assume you know, you can specify how many concurrent requests by setting maxConcurrentOperationCount when you create your queue. Four is a typical value.

self.imageDownloadingQueue.maxConcurrentOperationCount = 4;

The thing is, you can't go much larger than that (due to both iOS restrictions and some server restrictions). Perhaps 5, but no larger than that.

Personally, I wouldn't use up the limited number of max concurrent operations returning the text values. I'd retrieve all of those up front. You do lazy loading of images because they're so large, but text entries are so small that the overhead of doing separate network requests starts to impose its own performance penalties. If you are going to do lazy loading of the descriptions, I'd download them in batches of 50 or 100 or so.

Looking at your source code, at a very minimum, you're making twice as many JSON requests as you should (you're retrieving the same JSON in getAnimalRank and getAnimalType). But you really should just alter that initial JSON request to return everything you need, the name, the rank, the type, the URL (but not the image itself). Then in a single call, you get everything you need (except the images, which we'll retrieve asynchronously, and which your server is delivery plenty fast for the UX). And if you decide to keep the individual requests for the rank/type/url, you need to take a look at your server code, because there's no legitimate reason that shouldn't come back instantaneously, and it's currently really slow. But, as I said, you really should just return all of that in the initial JSON request, and your user interface will be remarkably faster.

One final point: You're using separate queues for details and image downloads. The entire purpose in using NSOperationQueue and setting maxConcurrentOperationCount is that iOS can only execute 5 concurrent requests with a given server. By putting these in two separate queues, you're losing the benefit of maxConcurrentOperationCount. As it turns out it takes a minute for requests to time out, so you're probably not going to experience a problem, but still, it reflects a basic misunderstanding of the purpose of the queues.

Bottom line, you should have only one network queue (because the system limitation is how many network concurrent connections between your device and any given server, not how many image downloads and, separately, how many description downloads).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

Have you thought about just doing this asyncronously? I wrote a class to do something very similar to what you describe using blocks. You can do this two ways:

  1. Just load async whenever cellForRowAtIndexPath fires. This works for lots of situations, but can lead to the wrong image showing for a second until the right one is done loading.

  2. Call the process to load the images when the dragging has stopped. This is generally the way I do things so that the correct image always shows where it should. You can use a placeholder image until the image is loaded from the web.

Look at this SO question for details:

Loading an image into UIImage asynchronously

Community
  • 1
  • 1
LJ Wilson
  • 14,445
  • 5
  • 38
  • 62
  • He is doing it asynchronously. And your serial GCD queue is going to be even less efficient than OP's `NSOperationQueue`. – Rob Mar 09 '13 at 14:14
  • Thx for the advice Rob. I will do some benchmarking using NSOperationQueue and see how much faster it is. The app I use that async loading class in has a 8000+ record dataset feeding it and is remarkably fast, but I am always looking for a better way :) – LJ Wilson Mar 09 '13 at 14:20
  • I find it's 25-30% faster downloading a bunch of images if I do it concurrently (this will be function of (a) size of images; and (b) latency of your network connection with your server). You basically diminish impact of latency issues. Make sure to limit concurrent operations to 4, or else some operations might time out. – Rob Mar 09 '13 at 14:23
  • On your first point, about the problem with `cellForRowAtIndexPath`, that is generally a result of one of two mistakes: 1. Failure to properly initialize image _before_ you dispatch image fetch update to background queue (and thus seeing the dequeued cell's image; or 2. Failure to double-check the `cellForRowAtIndexPath` before updating the image. I like your answer to that other question (with the `updateCell` caveat) and I think that `cellForRowAtIndexPath` is the best place to do it. Your separate class is elegant solution. Well done. – Rob Mar 09 '13 at 14:31
  • I take back my earlier comment about the performance difference. I just glanced at your code (which is creating a serial queue) and was assuming that you were doing your image retrievals on the same serial queue. But you're creating a separate serial queue (each sharing the same name) for each image! So, in effect, you're _not_ doing serial operations. I misinterpreted your use of a serial queues. Anyway, moving to NSOperationQueue won't yield huge performance improvement, but it will prevent time out problem that you'll get if you use your technique and try to download too many images. – Rob Mar 09 '13 at 16:25
  • I get around 10-15 images at once (depending on whether the user is using the iPhone or iPad version of the app). When I wrote this, I did extensive testing and could not get the process to misbehave. Granted the images I get are really small (100 X 100 pixel pngs). – LJ Wilson Mar 09 '13 at 18:02
  • Yeah, you'll probably never see the problem when dealing with files that small. They're finishing quickly enough that the problem won't manifest itself. But if you're dealing with large images (or other large downloads), you will have problems if you allow more than 5 concurrent operations. I just tested it in iOS 6.1 and the `NSData` method `dataWithContentsOfURL` apparently will retry for 1 minute before failing. So, for operations like tableview thumbnail images, you're going to be fine. But for general purpose background downloading, definitely limit concurrent operations to 4 or 5. – Rob Mar 09 '13 at 18:36
  • I have uploaded my code here : http://submission.ws/downloads/animals.zip I am already doing: self.imageDownloadingQueue.maxConcurrentOperationCount = 4; Can you figure out whats slowing the performance there and help me fix it? Everything works great if I just use the imageDownloadWQue for image downloads, but when I use the detaildownload que for the detail data , the performance slows down drastically. Hope this might help. – real 19 Mar 09 '13 at 19:30
  • Also How can I sort this table based on Rank number – real 19 Mar 09 '13 at 20:48
  • Ideally, you would do all your sorting on the web-services side of things (IMO). Sorting and manipulating data is much faster via SQL than doing it in code. If you have to sort in the application, using NSSet will help you. See this question for more info: http://stackoverflow.com/questions/15271577/two-array-retrive-both-value-in-one-first-array/15272013#15272013 – LJ Wilson Mar 09 '13 at 21:24