This workshop will be retired on May 31, 2020.
Operation Queues19:06 with Amit Bijlani
Operation and OperationQueue are a set of APIs built on top of Grand Central Dispatch. They are higher level APIs offering a bit more control and flexibility than GCDs lightweight APIs.
Operation and 0:00 operation cues are a set of APIs built on top of Grand Central Dispatch. 0:01 Their high level APIs offering a bit more control and 0:07 flexibility than GCD's lightweight APIs. 0:10 They offer features like dependencies, 0:14 where you can make one operation depend on the other. 0:16 For example, we want to download a photo and once it's downloaded, resize it. 0:19 There are other features like canceling operations, querying their state, and 0:25 finally, seeing how many operations are there in a queue. 0:29 In addition, your code can easily be debugged and 0:33 managed when using operation in operation queues. 0:36 The main reason you want to use operation and operation queues 0:40 is because you want to manage what's being processed in the background. 0:44 Unfortunately, with GCD, you sense something to be 0:48 processed in the background and then you don't really have much control over it. 0:50 Whereas with operations, you have a lot of control over the tasks. 0:55 In our example, we're downloading images. 1:00 Ideally, we want to download only the images for cells that are visible. 1:03 This will make our app a lot more efficient and performant. 1:08 So let's see how we can replace GCD for image downloading and 1:12 use operations instead. 1:16 So coming back to our project. 1:19 As we had previously seen, 1:21 we had modified our ImageTableViewCell to use a Grand Central Dispatch. 1:23 We accessed the global concurrent queue and 1:29 used the asynchronous method to download the image data. 1:32 Now instead of using Grand Central Dispatch in this example, 1:36 we're going to use operations and operation queue. 1:40 Now an operation is a unit of work. 1:44 So whatever we performed here would be part of our operation, 1:48 and operation queue is synonymous to the DispatchQueue. 1:53 Basically, we'll add our operation to the operation queue. 1:58 Now, the great thing about Grand Central Dispatch is that it's 2:01 a block-based interface, right? 2:05 So, you just provide a block of code and you dispatch it to a queue. 2:07 Whether it's the main queue, the global queue, any queue. 2:13 With operation and operation queue, it's not so. 2:16 Because operation and operation queues are built on top of GCDs, 2:20 they are more object oriented-based. 2:25 You have to subclass an operation and 2:29 then provide it the unit of work that you want to perform. 2:31 And then of course, send it off into the operation queue. 2:35 So, let's create this subclass. 2:40 And we'll say a new file, a Swift file. 2:43 And we'll say, image. 2:46 We'll just call it an imageDownloader. 2:49 We're going to import UIKit. 2:53 We'll say class imageDownloader, which is a subclass of operation. 2:59 We're going to have a constant of type photo. 3:05 We're gonna declare a designated initializer, which takes in a photo 3:12 and sets it to our constant. 3:22 So, the next thing, where our unit of work is performed, 3:27 just like in our ImageTableViewCell, we had all of this 3:32 going on where we download the image data from a specific URL. 3:36 That's exactly what we're going to do inside our ImageDownloader. 3:42 But where do we do this? 3:46 Here, we had this convenient async method where you just provided a block of code. 3:48 Well, in the operation, you have this method call main, 3:54 which we have to overwrite. 3:58 If you command click here, you can look at what this definition looks like. 4:00 And here you can see this main method. 4:06 This is where our unit of work will be performed. 4:09 There are other methods that you can note, which is start, isCancelled. 4:12 You have a Cancel method. 4:16 Of course, you can query it for a status. 4:18 You have methods like isExecuting, isFinished, isConcurrent, all right? 4:21 So we have to override the main method. 4:27 So first and foremost, the compiler is complaining that we need to provide it 4:31 with an override directive because we are overwriting it from the superclass. 4:36 So the first and foremost thing we need to check, if isCancelled. 4:41 So if this operation is cancelled, then we simply return. 4:46 We don't need to do anything here. 4:52 So we're gonna create another constants in another constant. 4:55 We're going to call this imageData = 4:59 Data(contentsOf: url, photo.imageURL). 5:04 All right, so while this data is being downloaded, 5:10 it's entirely possible that this operation was canceled once again. 5:14 So if it has been canceled, then we're going to check if it's been canceled, and 5:20 then return. 5:24 Now the compiler is complaining. 5:26 Let's see what it's complaining about. 5:27 It says, cannot convert value. 5:28 Right, so we need to provide it a URL cuz that is a string. 5:32 So I'm going to convert that. 5:35 And it's still complaining. 5:38 I think we need to implicitly unwrap this. 5:43 And it's still complaining. 5:47 So that's the warning. 5:48 Right. 5:51 So the error is called can throw but it is not marked with try. 5:52 So the error is not handled. 5:57 So this initializer actually throws, 5:58 so we need to put in a try. 6:04 So we'll just say try?,which will give us an optional. 6:07 And then, if it successfully downloaded the data, 6:10 then we're going to unwrap it and 6:18 say if let data= imageData, data.length >0. 6:23 And that's complaining, and says lend has been renamed to count, right? 6:30 So will use counts. 6:37 So basically, we're just checking to see we actually got some data. 6:40 And if we did, then we're simply going to add it to our ImageCache. 6:42 So you add photo.imageURL. 6:48 So we need to add an image. 6:54 So let's convert this to a UIImage. 6:58 We'll say let image =. 7:01 All right, so so far we don't have we're not implicitly unwrapping anything. 7:10 And this code is pretty safe. 7:16 We have an error somewhere. 7:19 Right, okay, so we need to implicitly unwrap the image. 7:22 But here we're already checking to see if we actually have data. 7:27 And if we do, then we're creating an instance of image. 7:30 And then we're going to add it to our cache. 7:33 Great, so as you can see, 7:37 this is a lot more involved than using Grand Central Dispatch. 7:39 But as I mentioned, there's a lot of advantages to using operations and 7:42 operation queues. 7:47 One of them is you can query it for state or status, as we had seen here. 7:49 You can check if it's executing, finished, 7:54 it's concurrent, it's ready, all of those things. 7:56 You can check the state of your operation. 7:59 You can cancel your operation. 8:02 You can check how many operations have been queued. 8:03 So, all of this operation management is being done. 8:06 It is a lot of heavy lifting if you have a lot of 8:11 concurrent tasks going on within your application. 8:14 So you want to use all of this management layer that the operation queue and 8:17 operation provides you with. 8:22 Because with the dispatch async, once the work has been dispatched, 8:24 then you really don't have a lot of control over it. 8:28 All right, so going over to our ImageTableViewCell. 8:31 What we had here was in our configure method, we got our image from the cache. 8:37 And if we didn't have it in the cache, then we loaded from the network. 8:42 So we're going to do something very similar here. 8:46 We're going to use the operation that we just created, 8:47 the ImagDownloader operation. 8:52 So we're gonna create a property that simply says 8:55 ImageDownloader, Of type ImageDownloader. 9:00 And this will be an optional. 9:07 So instead of saying, load image, we'll say, 9:11 set up or configureImageDownloader(for photo). 9:17 So let's copy this method signature. 9:30 And we're going to create a new method that doesn't return anything. 9:32 And basically it takes in a photo of type Photo. 9:40 You have to put in a : here. 9:44 So we're gonna configure an ImageDownloader for a photo. 9:47 Sorry we're passing a photo here, all right. 9:51 So say let downloader = ImageDownloader, 9:53 and we pass in a photo, great. 10:00 Now what happens after it downloads a photo? 10:06 Just like here, once it downloaded a photo we set it to the imageView, 10:12 we have to do the exact same thing here. 10:15 So we'll say, downloader.completionBlock. 10:17 We can say once you have completed whatever operation you're doing, 10:21 in our case once you've downloaded the image, you have to do a bit more work. 10:26 Whenever you're doing work inside an operation, 10:32 you always have to check if that operation has been cancelled. 10:35 So you have to check to see 10:38 if downloader.isCancelled, then simply return. 10:41 If not, then do something. 10:48 Well, in our case, that something is setting the ImageView with the image. 10:50 Now, we could have created a property within downloader 10:56 to download it to an image property. 11:00 But since we're saving it inside our cache, because we're still using 11:02 that caching mechanism, we're just going to get it from the cache. 11:06 Now remember, the operations are being done on a background queue. 11:09 So if we're updating the UI, we have to do it on a main queue. 11:14 So I'm going to just copy this code here. 11:18 And I will paste it here. 11:22 Now, since we don't have the image, we're going to get it from our ImageCache. 11:24 So, we'll say, ImageCache.shared.get, oops. 11:27 And we'll pass it the photo.imageURL(). 11:34 All right, so, we've configured our operation. 11:40 We have our operation property for our imageTableViewCell. 11:44 And we're no longer using this loadImage method, we can even comment it out here. 11:50 We'll just comment that out so there 's no confusion. 11:55 And then going back to our PhotosTableViewController. 11:58 Now to manage operations, we need an operation queue. 12:02 Now creating an operation queue is very straightforward. 12:06 We'll create a lazy property. 12:10 So we'll say lazy var downloadQueue is equal to, 12:11 well, is of type OperationQueue. 12:17 And I create a queue. 12:27 We'll create a variable called queue of type OperationQueue. 12:30 We're creating a lazy property here because we don't want it to be 12:37 instantiated unless it's necessary, or until it's time for it to be instantiated. 12:42 It's important that your name your queue so 12:50 that the name shows up in instruments or in debugger. 12:52 So we're gonna call this the Image Downloader Queue. 12:55 Now one thing we can do since we're debugging and 13:05 testing this operation queue out, we can set the maxConcurrentOperationCount to 1. 13:08 What this will do is it'll allow us to see the operations in 13:14 our queue finishing one-by-one. 13:19 Of course, you must leave this part out if you're building the application for 13:22 production. 13:26 Because you want to let the system handle how many operations it can do it at once. 13:27 So how do you decide how many operations it can run at once? 13:34 Well, that all depends on the hardware, it depends on the resources, it depends on 13:37 the number of cores in the CPU, on the amount of memory available at that time. 13:42 So, it's best for 13:47 the platform to decide how many number of threads it can create at once. 13:49 And we should not be making that determination. 13:56 And then, finally, returning the queue. 14:00 And we need the open and close parentheses so that it's going to execute this block 14:04 of code when we ask for our downloadQueue. 14:09 All right, so we've created the downloadQueue. 14:15 Lastly, we need to add our download operation to our downloadQueue. 14:18 So we're going to do that in our cellForRowAt indexPath method. 14:24 So after we have configured our imageTableViewCell, 14:28 we're going to check to see if we have a downloader. 14:33 So, we'll say, if let downloader = imageTableViewCell.ImageDownloader. 14:38 So, if we do have a downloader, we're going to add it to our downloadQueue. 14:46 So, we'll say, add operation(downloader). 14:52 All right, one thing that I forgot to do was in our imageTableViewCell. 14:57 Once this downloader has been configured, every time it dequeues, 15:02 it's still going to be configured. 15:06 So we need to set it to nil every time a imageTableViewCell is being dequeued. 15:09 Why is that so? 15:17 Because it's associated to that specific image. 15:17 Remember, the cells are being reused over and over again. 15:22 We don't create, if we have 1,000 JSON objects in our photos' JSON, 15:27 then that doesn't mean that 1,000 cells are being created. 15:33 At the most it's whatever amount of visible cells there are, like eight, 15:39 ten, which are being reused over and over again. 15:44 So, when we're reusing a cell, we want to make sure that the ImageDownloader 15:48 has been cancelled and set to nil. 15:52 So, we're gonna do self.imageDownloader?.cancel. 15:55 And then, self.imageDownloader = nil. 16:01 Great, so, we're reusing our ImageDownloader and 16:06 we're setting it to nil. 16:10 We're configuring it when necessary. 16:11 And we're adding it to the downloadQueue when we're retrieving the cell. 16:14 So, let's run our application to see if we successfully implemented operations and 16:20 operation queues. 16:25 And, it looks like something went wrong. 16:30 So, let's go back to our cell. 16:33 And we're checking to see if we get it from cache. 16:36 If not, we're configuring our ImageDownloader. 16:40 And we're creating this local variable called downloader, but 16:44 we're never setting it to our property. 16:48 [LAUGH] So, do not forget that folks. 16:50 So self.ImageDownloader = downloader. 16:55 Perfect, now lets run our application and see if this marvel works. 16:58 All right, we're not losing user activity. 17:06 So our user interface is responding to touch events and scroll events. 17:10 Now we can scroll. 17:18 And as you can see, this is responding pretty well. 17:20 It's executing the downloading in the background, and as cells become visible, 17:25 it is showing us the necessary photo that belongs to that particular cell. 17:30 And I'm scrolling pretty fast here. 17:36 And as you can see, there's no loss in frames. 17:40 There's no lag. 17:44 There's no stuttering. So there you have it, 17:46 we successfully implemented operation and operation queues, 17:48 which provided similar functionality to our Grand Central Dispatch approach. 17:53 Concurrency is a powerful tool to take advantage of the computing power of 18:01 modern multi-core CPU's. 18:06 GCD or operation queues make it easier to execute more code concurrently. 18:07 However, these technologies do not guarantee to make our apps more efficient 18:14 or responsive. 18:18 It is our responsibility as developers to know what is going on with our code and 18:20 the side effects we're introducing. 18:25 Before introducing concurrency, study your application's needs to understand where 18:28 and how introducing concurrency will benefit the performance and 18:33 responsiveness of your app. 18:37 It's not a be-all, end-all solution, but 18:39 it is yet another important tool in our tool belt as developers. 18:42 So make sure you understand it wisely and use it carefully. 18:47 When carefully implemented, 18:51 concurrency has great advantages to the performance of your application. 18:52 As always, 18:58 check out the teacher's notes to learn more about this amazing technology. 18:58 This is Ahmed signing out until next time. 19:02
You need to sign up for Treehouse in order to download course files.Sign up