Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

Operation and OperationQueue Tutorial in Swift

In this tutorial, you will create an app that uses concurrent operations to provide a responsive interface for users by using Operation and OperationQueue.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5293-operation-and-operationqueue-tutorial-in-swift

Great tutorial - I’m going to apply it to a pet project that’s currently using DispatchQueues and semaphores.

There are a couple of opportunities for slight refinement though…

I don’t think instantiating an empty dictionary ranks amongst the most expensive operations, so there’s two lazys that aren’t achieving a lot:

lazy var downloadsInProgress: [IndexPath: Operation] = [:]
...
lazy var filtrationsInProgress: [IndexPath: Operation] = [:]

This is ignoring one of the key features of optionals:

if let pendingDownload = pendingOperations.downloadsInProgress[indexPath] {
    pendingDownload.cancel()
}

… so can be reduced to just this:

pendingOperations.downloadsInProgress[indexPath]?.cancel()

And finally, this:

pendingOperations.downloadsInProgress.removeValue(forKey: indexPath)

… can be stated more concisely:

 pendingOperations.downloadsInProgress[indexPath] = nil

Arguably that last one doesn’t make it as as instantly obvious that the key/value pair is being removed from the dictionary (but it is - see the docs). It does neatly mirror the corresponding guard check though:

guard pendingOperations.filtrationsInProgress[indexPath] == nil
1 Like

@jgoodwill Do you have any feedback regarding this? Thank you - much appreciated! :]

Different issue: not getting any failures! What I mean is that this line in the description says that we should get some unloadable URLs:

“Some images in the data source are intentionally mis-named, so that there are instances where an image fails to download to exercise the failure case.”

I took a look at the downloaded list of images, and the 4 entries at the end have “NO URL” as the url.

At this point in fetchPhotoDetails, those are effectively filtered out:

   // 4
    for (name, value) in datasourceDictionary {
      let url = URL(string: value)
      if let url = url {
        let photoRecord = PhotoRecord(name: name, url: url)
        self.photos.append(photoRecord)
      }
    }

URL(string: value) is returning nil for them, because “NO URL” is not an allowable URL, due to the blank space. It can be fixed like this, making the blank space into an underscore:

      for (name, value) in datasourceDictionary {
        let fixedUrl = value.replacingOccurrences(of: " ", with: "_")
        let url = URL(string: fixedUrl)
        if let url = url {
          let photoRecord = PhotoRecord(name: name, url: url)
          self.photos.append(photoRecord)
        }
      }

But that’s not all. You also have this in func main() of ImageDownloader:

//5
guard let imageData = try? Data(contentsOf: photoRecord.url) else { return }

Because it returns when the url is no good, it never gets a chance to set the state of photoRecord.
So you need this instead:

//5
let imageData = try? Data(contentsOf: photoRecord.url)

and a little further down, change the last bit to this:

//7
if let imageData = imageData {
  photoRecord.image = UIImage(data:imageData)
  photoRecord.state = .downloaded
} else {
  photoRecord.state = .failed
  photoRecord.image = UIImage(named: "Failed")
}

Now you can have some failures downloading! You will get a nice red warning image for the likes of “Flying Taxi” and “Chocolate House”. I tweaked the cellForRow switch on state to at least show the failing name:

case .failed:
  indicator.stopAnimating()
  cell.textLabel?.text = "Failed to load \(photoDetails.name)"

Great stuff! I am going to use this to adapt a personal project as well! I was really wondering what is the right approach to do this!

1 Like

Hi the link for the dataSourceURL on the starter project is unavailable, making the app crash.

let dataSourceURL = URL(string:“http://www.raywenderlich.com/downloads/ClassicPhotosDictionary.plist”)!

Thanks!

1 Like

@jgoodwill Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi wilf, I just tested the app again and downloaded the plist directly. I found it with no problem. I also clicked the link listed in your comment and it worked fine. Would you mind trying again?

Thanks chrisji, We will look at applying your changes when the tutorial is updated.

I have a quick question as I am really struggling with this. What if instead of downloading an image I have a URL request which is async - the operation will be considered completed after triggering the request, instead of getting a response. How can I do it only when I get a response instead?
eg:
“//5
guard let imageData = try? Data(contentsOf: photoRecord.url) else { return }”

I am not sure if I explained this correctly - please do let me know if I’m not making any sense.

Thanks in advance!

I think I found an answer from a different tutorial: https://www.raywenderlich.com/4250-rwdevcon-2017-vault-tutorials/lessons/4

There are also tutorial playgrounds files - the gist of it is you need to manually manage the state of the operation - and you are going to set it to .finished in the completion handler of the request.

Great stuff! Love this website!

Hi, Thanks for the great tutorial.
I am trying to use the code in a similar situation , but I find a small difficulty. In the ImageDownloader
we use the main() and on the startDownload( : : ) we use the completionBlock of ImageDownloader.
Everything fine so far. This works if we use a Synchronous Data Downloading for the UIImage. In case of Asynchronous downloading we can’t use that similar code!

Any suggestions on how to solve this issue?
If we use async - Data Downloading for the UIImage, we end up FIRST in completionBlock and then the we get the data which is not the scenario we want.

Hi @scy - take a look at the video I’ve linked to in my previous post. There are a series of tutorials that teach how to implement async tasks with Operation Queue on this website that you can check out. The gist of it - you need to control the state of the Operation manually, so you will set it to .finished when you get the response instead of it setting itself automatically finished after triggering the request.

There is a really good video tutorial which covers it on this site. However it does requires a subscription unfortunately (worth it imo). I also found some info in Chapter 4 of the Apple free book: App Development with Swift, that you may want to check out.

HI @el_cid, thanks for the reply. I will check that video that you posted , I already had it in my watch list , but now there is a reason to watch it soon !!! I tried to find the video that you recommend that needs the subscription but I didn’t find something with operations. Can you send the link pls ?

Thank you for great Tutorial.
I am enjoying it.
Excellent examples, good writing, all I can say is keep up the good work.
One small note I have noticed a few days ago. It looks like http://www.sxc.hu is down, so it is impossible to download pictures anymore.

All best and have fun codding everyone.

1 Like

Hi, I’m also can’t download the images the site is down.

@jgoodwill Can you please help with this when you get a chance? Thank you - much appreciated! :]

how we can add cache if already image is load we will not load again i having this problem every time i come back from other VC it loading from 0
thanks

Image URL used in tutorial are not working. No images are loaded. Please update tutorial with updated image urls.

@jgoodwill Can you please help with this when you get a chance? Thank you - much appreciated! :]