

UI responsiveness of your iOS application by executing such time-consuming pieces of code as downloading data from the network or image processing, then you need to use advanced patterns related to multithreading ( oncurrency ), otherwise the work of your user interface ( U I) it starts to slow down a lot and can even lead to its complete “freezing”. You need to remove resource-expensive tasks with main thread (main thread), which is responsible for executing code that displays your user interface ( UI ).Swift 3 and the nearest Swift 4 (autumn 2017), this can be done in two ways that are not yet related to Swift built-in language constructs, which will only be implemented in Swift 5 (end of 2018).GCD (Grand Central Dispatch) and the previous article is devoted to it. In this article, we will show how to achieve responsiveness to UI in iOS applications using such abstract concepts as Operation Operation and the Operation operation queue. We will also show the difference between these two approaches and which of them is better to use in what situations.Operation ? A good definition of Operation given in NSHipster:Operationis a complete task and is an abstract class that provides you with a flow-safe structure to simulate the status of an operation, its priority, dependencies on otherOperationsand control this operation.
OperationOperation can be represented by a normal closure, which can also be executed on a DispatchQueue . But this form of the operation can only be applied provided that you add it to the OperationQueue using the addOperation method:
Operation can be constructed using a BlockOperation initializer. It is launched with its own start () method:
subclass class Operation and get its instance:
FilterOperation operation of obtaining a blurred image using the appropriate filter is defined as a user-defined subclass of the Operation class. You can see that a custom class can have both input and output properties, as well as other auxiliary functions. To accommodate the functional part of the operation, we overridden ( override ) the main () method.Operation class allows you to create some task that you can run on the OperationQueue queue in the future, but for now it can wait for other Operations .Operation has a state mashine , which is the “life cycle” of Operation Operation :
Operation : pending (pending), ready (ready to execute), executing (executed), finished (finished), and cancelled (destroyed).Operation and place it on an OperationQueue , you set the operation to pending . After some time, it takes a ready state (ready for execution), and at any time can be sent to OperationQueue for execution, switching to the executing state, which can last from milliseconds to several minutes or longer. After completion, Operation Operation enters the final state of finished . At any point in this simple “life cycle”, Operation Operation can be destroyed and will transition to the cancelled state.API Operation class API reflects this “life cycle” of the operation and is presented below:
Operation for execution using the start() method, but most often we will add an operation to the OperationQueue operation queue, and this queue will automatically start the operation. It should be remembered that a separate Operation Operation , launched using start() , is performed SYNCHRONOUSLY on the current thread. In order to run it outside the current thread, you need to use either OperationQueue or DispatchQueue .Operation at any point of the application can be monitored using Boolean properties: isReady , isExecuting , isFinished , isCancelled using KVO ( key-value observation ) mechanisms, since the operation itself can be performed on any stream, and we may most likely need information on the main thread ( main thread ) or on any other thread other than the one on which the operation itself is performed.Operation , we need to create a subclass Operation . In the simplest case, in this subclass we need to override the main() method of the Operation class. The Operation class itself automatically manages the change in the status of the operation, but in more complex cases presented below, we will have to do it manually.completionBlock , which is performed after the operation is completed, as well as with the “quality of service” qualityOfService , which affects the priority of the operation to be performed on the OperationQueue .Operation class has a cancel() method; however, using this method only sets the isCancelled property to true , and what semantically means “deleting” an operation can only be defined when creating a subclass Operation . For example, in the case of downloading data from a network, cancel() can be defined as disabling an operation from a network interaction.OperationQueueOperationQueue operation queue. The queue of OperationQueue operations can be viewed as a high-priority “wrapper» of DispatchQueue , endowed with additional functionality: the ability to destroy operations performed, perform dependent operations, etc.API of the OperationQueue class:

OperationQueue () operation queue and two class properties: current and main , which specify the current OperationQueue.current and main queue operations — OperationQueue.main , which is used to update the user interface ( UI ) similar to DispatchQueue.main in GCD . A very important property maxConcurrentOperationCount sets the number of simultaneous operations on this queue and, setting it equal to 1 , we set the serial ( serial ) queue of operations.
maxConcurrentOperationCount property maxConcurrentOperationCount set to Default , which means the maximum possible number of simultaneous operations:
OperationQueue operation (or any of its subclass ), a closure, or a whole array of operations with the ability to block the current thread until the full array of operations is completed.qualityOfService priority, “readiness” (the isReady property isReady set to true ) and dependencies ( dependencies ) on other operations. If all of these characteristics are equal, then the operations are sent to "execute" in the order in which they were queued. If an operation is placed in a queue of operations, then it cannot be placed again in any of these queues. If an operation has been performed, it cannot be re-executed on any of the operation queues, the operation is a one-time thing, so it makes sense to create subclasses of the Operation class and use them, if necessary, to re-receive an instance of this operation.cancel() message to all operations in the queue using the cancellAllOperations () method, for example, if the application “goes” to the background ( background ) mode. Using the waitUntilAllOperationsAreFinished() method, you can block the current thread until all operations on this operation queue have been completed. But NEVER do this on the main queue . If you really need to do something only after the completion of all operations, then create a private serial queue and wait for the completion of your operations there.DispatchGroup . You can add operations with different qualityOfService to the OperationQueue , and they will be launched according to their priority. You can also set qualityOfService at a higher level — for the operation queue as a whole, but this value will be overridden by the qualityOfService value for a particular operation.qualityOfService for OperationQueue is .background .OperationQueue by setting the isSuspended property to true . The operations performed on this queue will continue, but the newly added ones will not be sent for execution until you change the isSuspended property to false . The default value for the isSuspended property is false .Operation operation queue on the Playground .OperationQueue and Adding ClosuresOperationQueue.playground on Github .printerQueue :
printerQueue :
printerQueue , and they are in the ready state. We estimate the execution time of all operations using the waitUntilAllOperationsAreFinished() method, which, synchronously with respect to the current thread, waits for the end of operations. On the main queue in the application it is better not to do this, but on our Playground nothing happens to U I and we can afford it. The total execution time of all 7 operations is slightly more than 2 seconds and corresponds to the execution time of the sleep(2) operator, therefore, printerQueue runs all 7 operations simultaneously on many threads.printerQueue queue printerQueue , and set the maxConcurrentOperationCount property to 2 :
printerQueue , and a little more than 8 seconds to perform all operations, since they start in pairs and the last 7th operation starts alone in the fourth “pair”.concatenationOperation operation with an increased qualityOfService equal to .userInitiated :
printerQueue to serial ( serial ) by setting the maxConcurrentOperationCount property to 1 :

FilterOperation , familiar to us from the previous section, is applied:
filterQueue for performing filtering operations and a serial ( serial ) appendQueue for appendQueue -safely adding the filtered image to the array. The fact is that many filtering operations will simultaneously access a shared ( shared ) resource — an filteredImages array — to add their own element. Remember? We used a serial ( serial ) DispatchQueue queue to change shared resources? Here is the same, but will be performed for Operation . Of course, the serial ( serial ) DispatchQueue queue will be more efficient, but we will show the creation and use of the serial ( serial ) OperatioQueue queue.Swift arrays are value type and are copied when writing ( copy on write ), so you do not have to worry about multi-threaded changes, but there are messages in the forums that there are problems with this, especially if the array is a property of an object of type class. Therefore, we show a way to preserve the multithreaded security ( thread safe ) of an array when adding new elements to it in different threads.
filterQueue for asynchronous execution. After the filtering is done, we add the resulting image to the filteredImages array and do this in the completionBlock , where we use another queue of operations, appendQueue . The completionBlock no input parameters and it returns nothing.
filterQueue queue. On the Playground we see an array of filtered images, but they are very small to see the filtering effect. We can click on the quick view button and see the filtering effect:
AsyncOperations.playground on Github .completionHandler to close on another thread. A classic example is URLSession :
URLSession functionality in Operation Operation , but we will have to manually manage the operation states.main() operation. If we do the same with the ASYNCHRONIC function, then in main() it will immediately return control to the current thread and “leave” to work on another thread, and we will end up at the end of the main() method and OperationQueue immediately “throw” our operation out of the queue, without completing our ASYNCHRONOUS function. This is the logic of OperationQueue .
isReady = true ), the OperationQueue operation queue calls the start() method, in which we must set the operation to the “running” state ( isExecuting = true ) and call the main() method, which in turn will call the ASYNCHRONOUS function . The ASYNCHRONIC function performs something on an OTHER stream, but the isExecuting property must remain true , even if it does not perform anything on the CURRENT stream, but only represents a task that is running on another thread. When the ASYNCHRONOUS function calls completionHandler , which indicates the end of the ASYNCHRONOUS function, we must set the completionHandler property to isFinished , true , and the isExecuting property to false .override ) more than just the main() method. We need to override the following methods and properties:
AsyncOperaton , inherited from Operation and suitable for performing any ASYNCHRONOUS operation. Its abstract nature lies in the fact that it will not have a main() method to perform an asynchronous operation. Here is his diagram:
OperationQueue , then you need to override the isAsynchronous property and return true . We need to override the start() method to actually start the ASYNCHRONOUS function and keep the isExecuting property isExecuting to true . We also need to learn how to manage the properties that determine the status of the operation: isReady , isExecuting , isFinished . These properties use the OperationQueue operation queue to track the status of operations and organize the execution of dependent operations.OperationQueue operation queue about the actual end of the ASYNCHRONOUS function. If we recall GCD , then when adding an ASYNCHRONOUS function to a group, we clearly marked the beginning and end of the ASYNCHRONOUS function using the enter() and leave() methods. But in the case of Operation Operation situation is much more complicated, since the operation has states: isReady , isExecuting , isFinished , isCancelled , etc .. When adding an ASYNCHRONOUS function to Operation , we must manage these states manually. In order to facilitate this work, we have created a special abstract user subclass of the Operation class with an AsyncOperaton , whose main task is to control the change of the operation state. For your own ASYNCHRONOUS function, you will create a subclass class, defining there only main () , from which you will call your ASYNCHRONOUS function. And this new operation will be added to OperatonQueue .isExecuting = true , because all the properties associated with the state of the operation: isReady , isExecuting , isFinished , are readonly ( {get} ), and we cannot set them directly . We can only do something that will cause the isExecuting property to return true , while informing the system that the status of the AsyncOperaton operation AsyncOperaton changed.Operation class uses the KVO ( key-value observation ) mechanism and the willChangeValueForKe and didChangeValueForKey methods and notifications about changes in state properties like isReady , isExecuting , isFinished .enum Stateto represent the state of the asynchronous operation its own variants: ready, executing, finished.
Statealso contains a fileprivateproperty with a name keyPath, which we will use as a switch for KVO notifications. The property keyPathis computed and is made up of a string "is"connected to rawValue, which is the name of the enumeration element Statewith a capital letter.AsyncOperatonvariable in the abstract class to represent the current state of the operation; by default, this value is . Every time we change a variable, we need to switch KVO notifications. We will do this with the help of Observers and properties :var stateStatereadystatewillSet {}didSet {}state
state, for example, from executingto finished, we need to send KVO notifications willChangeValueabout the upcoming change of both operations: the new state newValue( finished) and the current state state( executing). After the state switch statehappens, we send KVO notifications didChangeValuefor keyPathboth states: oldValue( executing) and state( finished).isReady, isExecuting, isFinished. Therefore, the asynchronous operation AsyncOperationwe need to redefine the "native" state variables operations isReady, isExecuting,isFinishedusing the new property stateas calculated variables that return the correct values. We will do this in a extensionclass extension AsyncOperation:
isReadybecomes equal true, and we must use the property isReadyfor superclass, which “perceives” dependencies ( dependencies) on other operations. By combining our own logic with property isReadyfor superclass, we can be sure that the operation is really “ready.” Note that if a variable stateis equal .finished, then the property superclass isFinishedis equal trueand the property isExecutingis false. We will also override the property isAsynchronousby returning true, and two methods: start()and cancell().start()we check whether the operation is destroyed, that is, the property isCancelledis equal true. If so, then we need to set a new value for the variable.state- .finished. If the operation is not destroyed, we call main(). Remember that there main()is an ASYNCHRONOUS function, and it returns immediately, so we should manually set the operation state statto be equal .executing. When the ASYNCHRONOUS function returns completionHanller, in it we need to set the status of the operation .finished. It is very important to remember that in the case of an ASYNCHRONOUS function, we cannot use in the method a start()call to a similar method for superclass- super.start(), since this would mean a synchronous start of the function main(), but we need exactly the opposite.cancell()we also need to set the status of the operation .finished.AsyncOperation, and we can use it for our own ASYNCHRONIC operations. To do this, you must perform the following procedure:
sleep (1)) the addition of two numbers:
AsyncOperationas superclass, we have to redefine main ()and remember to set the operation state statein .finishedto callback:
callbackreturns the result resultthat we assign to the operation property self.resultand set the operation state stateto .finished, which informs the operation queue about the completion of asynchronous addition of numbers and that it is no longer necessary to work with this operation.SumOperationto get an array of the sum of pairs of numbers:
SumOperationand place it in the operation queue in the additionQueueusual way. We see that the order of execution of asynchronous operations is slightly different from the order of the pairs of numbers in the array. It speaks about multi-threaded execution of our asynchronous operations.URLby URLSession:
ImageLoadOperation, which, like last time, is a subclass of a class AsyncOperation. For the operation ImageLoadOperation, as well as last time, we redefine main ()and do not forget to set the status of the operation statein .finishedto completion:
operationLoad, get the image operationLoad.outputImageand map to view:
AsyncOperations.playgroundon Github .dependencies)

OperationQueuewill know that the “filter” is set to the state readyonly after the “loader” operation ends. This is a truly remarkable "ability" queue of operations OperationQueue. You can create a very complex graph of "dependencies" and thus force the OperationQueueimplementation to automatically launch operations as you need. APIclass Operationthat supports work with "dependencies" (dependencies) - very simple, but reveals a fantastic power when working.
dependency), as well as get a list of “dependencies” dependenciesadded for this operation. Below we will use the list dependenciesto get the input image of the dependent filtering operation.

ImagePassthat will supply us with the necessary data, in our caseUIImage? :
ImageLoadOperationimage loading operation already known to us from the network. At the input, the URLimage is set as a string urlString, and the output is the image itself outputImage.
ImageLoadOperation" confirms "the protocol ImagePassand returns the imageoutput loaded image as a protocol property outputImage.FilterOperation- in the absence of an input image, it _inputImageanalyzes its" dependencies " dependenciesand is interested only in those that" confirmed "the protocol ImagePass. He chooses the first such dependent operation and extracts his own inputImage: From the
inputImage, and the output is the filterImage (image:)image filtered by the function outputImage. This is a normal synchronous operation, so we only need to redefine it main().inputImageoutput image of the “load” operation as the input image . To do this, we initialize the filtering operation filterwith the value nil:

LoadAndFilter.playgroundon Github .OperationQueueCancellation.playgroundon Github .OperationQueueopportunity - the ability to destroy operations.Operationin the queue OperationQueue, you have no way to influence its execution, since the operation queue has its own plan for launching operations and it completely controls your operation. But you have the ability to destroy Operationusing the method cancel().
cancel()will instantly stop the operation, but it is not. The method cancel()only sets the isCancelledoperation property to true. If the operation has not yet started, then the default methodstart()will not allow the operation to be performed and set its property isFinishedto true. If you override the ( override) method start(), then you must retain the ability of your to start()prevent the operation from starting if the property of the operation is isCancelledset to true. And if you look at the abstract class AsyncOperation, you will see that we did just that.main()operation method , especially before doing something slow or resource-consuming, you need to test the property isCancelledto determine whether the operation has already been destroyed. And if the operation is destroyed and it shows the value of the trueproperty isCancelled, then you have to carry out the necessary actions to stop the operation. If operationOperationperformed locally, for example, image conversion, then you can stop the operation. If the operation is related to accessing the network, such as downloading an image from a server, then you cannot stop such an operation until the server returns the result to you.isFinishedequal state true.APIclass Operation, designed to remove the operation is very simple and consists of only two positions:
cancel()— it sets the property of the isCancelledoperation to true. It is important to note that when a method is called cancel(), the properties isExecutingand isFinishedalso change to falseand, truerespectively.
isCancelled = true) and will not end ( isFinished = true). The property isCancelledinforms the operation that it should stop, and the property isFinishedinforms the system that the operation has already been stopped.AsyncOperationredefines the method cancel()in such a way that it sets the state of the operation stateto .finished, and this change leads to a change in the properties of the operation isFinishedand isExecuting:
OperationQueuecan destroy all operations:
cancel().Cancellation.playgroundon Github .ArraySumOperationhas an input array of inputArraytuples consisting of a pair of integers and forms an array of outputArraytheir output totals:
slowAddpresented in the folder Sourceon the Cancellation.playground and add to the output array outputArray.sumOperation, add it to the operation queuequeueand start the timer, which will allow us to further regulate the time after which we will be able to check the operation response sumOperationto the method call cancel(). In addition, the operation has completionBlock, in which we stop the timer, show on Playground outputArrayand complete the work on Playground:
sumOperationtakes a little more than 5 seconds. Now we will try to destroy this operation, 2 seconds after the start, calling the method cancel ():
sumOperationcompleted, no operation was destroyed. What is the matter?But the fact is that the method cancel ()only sets the property isCancelledto true, and the actions necessary to delete the operation fall on the developer of the operation. We need to respond to the fact that the property is isCancelledset to true. We will loop through each addition to the output array to check whether the operation is not destroyed. And if destroyed, then we interrupt the cycle:
Playground:
cancel ().AnotherArraySumOperation, which differs in that another function is used slowAddArrayto get the output array of a loop through the array of tuples:
main()operation method , but in another function and is difficult for us abort the loop if the operation is destroyed. But there is such a possibility, although it is very sophisticated:
slowAddArrayarray of inputpairs of integers, in addition, it has an argument progress, which is a Optionalfunction that takes a variable depth of array processingDouble(results.count) / Double(input.countBool. This Booldefines the continuation of array processing.main()operations AnotherArraySumOperation(the previous figure), we passed an slowAddArrayarray to the function inputArray, and the argument was progressformatted as a “tail” closure, in which we used the property progressfor printing. The property progressis Double, so we multiplied it by 100 and got% of the end of array processing and completion of the operation. Then we return the response to the destruction of the operation, which is a signal to continue or interrupt array processing. A reaction is an inversion of a property isCancelled.SumOperationwith a new operation AnotherArraySumOperation:

isCancelledcancel ()OprationQueuecancellAllOperations. This is especially useful if you have a set of operations that work for a single purpose. This goal may be to run multiple independent operations in parallel or to represent a graph of dependent operations executed one after another. Consider both of these cases.CancellationGroup.playgroundon Github .ArraySumOperationpresented in the previous section. This operation takes an array of tuples (Int, Int), and, using the slow addition function slowAdd(), creates an array of sums of the numbers that make up the tuple. The loop on the components of the input array is hidden inside ArraySumOperation. Let's create a group of individual very simple type operations SumOperation. The operation SumOperationadds two numbers from the input pair inputPairusing the slow addition function slowAdd()and returns the result output:
GroupAddthat manages the privatequeue of operations queueand a variety of operationsSumOperationin order to calculate the sum of all pairs in the input array and place in the output array outputArraytuples ( Int, Int, Int )consisting of input data and result:
GroupAddinput array of inputpairs of numbers is formed from which type operations are formed SumOperation. In completionBlockeach operation, the result is added to the output array outputArray, which is performed on a single privateserial line operations appendOperationto avoid race condition .Operationmethods: start(), cancel (), wait (), so we can consider it as a "complex operation".GroupAdd, giving the input an array of pairs of numbers:
groupAdd, wait for 1 second and use the method cancel ()to remove all the summation operations from the operation queue. As a result, after the completion of all operations (we use wait(), which can NOT be used on main queue, but can be on Playground), we get a shortened output array:
Playground CancelletionGroup.playgroundon Github .CancellationFourImages.playgroundon Github .UI. Let's try to arrange this sequence into a separate class ImageProviderthat will manage these operations on OperationQueueusing the methods start (), wait ()and cancel ().main()). One is the asynchronous operation already familiar to us AsyncOperation, and the other is the operation of ImageTakeOperationextracting the input image inputImagefrom the dependencies dependecies.AsyncOperationcreate an image download operation from the “network” at the specified URL address:
ImagePassfor transmitting the resulting image outputImage further along the chain of operations.ImageTakeOperationextracts the input image inputImagefrom the dependencies dependecies, if it is not specified when initializing this operation, and allows you to “pick up” the output image using the already familiar protocol ImagePassused to transfer images in a chain of sequential operations: The
ImageTakeOperationvery convenient to use as an superclassoperation. participating in a chain of dependent operations. For example, for filtering operation Filter:
PostProcessImageOperation:
ImageOutputOperation:
ImageProvider. Create the regular class ImageProviderthat controls the privatequeue operations operationQueue, and the sequence of operations dataLoad, filterand outputto load an image from a given the URL , filter it and pass in the circuit completion:
ImageProviderhas all the typical operation Operationmethods: start(), cancel (), wait (), so we can consider it as a "complex operation ".ImageProvider:


cancel ()to delete all operations. As a result, we get the download of only three images - 1st, 3rd and 4th:
CancelletionFourImages.playgroundon Github .OperationTableViewControlleron Github .main queue. Let's consider the application of the class presented in the previous section ImageProvider, which performs a group of interrelated operations already known to us for downloading an image from the network, filtering and modifying it UI.Image Table View Controller, whose table cells contain only images downloaded from the Internet and an activity indicator showing the loading process:
ImageTableViewControllerthat serves the screen fragment looks like Image Table View Controller: The
ImageTableViewControlleris an array of 8 URLs :
ImageTableViewCellfor the cell of the table into which the image is loaded has the form:
Public APIthis class is a string imageURLStringcontaining the URL address of the image. But if we set imageURLStringnot equal nil, then the image will not be loaded, only the indicator in the form of a “spinning wheel” will start working. But if we already have a somehow loaded and processed image image, then calling the method updateImageViewWithImage, we will show it in this cell on the screen with the help of a light animation. In this class there is an activity indicator spinnerthat starts if you assign a imageURLStringvalue in the method tableView( _ : cellForRowAt:).UITableViewDelegateresponsible for the interaction of the cell UITableViewCellwith the table.UITableView :tableView( _ : cellForRowAt:) ,tableView( _ : willDisplay:forRowAt:) ,tableView( _ : didEndDisplaying:forRowAt:) ,
ImageProviderwill be moved out of the method tableView( _ : cellForRowAt:)to make this method as easy as possible. It will be located in the tableView( _ : willDisplay:forRowAt:)delegate method that prepares the cell to become visible. Another tableView( _ : didEndDisplaying:forRowAt:)delegate method will be used to destroy any request to load an image, which it will not have completed by the time the cell leaves the screen. This is a fairly general approach and can be used in any application that works with TableView. This will improve the scrolling performance of the table.ImageProviderthat will be used in this application. Unlike the class variant ImageProviderthat was used in the previous section onPlayground, we will use its simplified form. Namely, we do not need to ImageProviderstop ( isSuspended = true) the operation queue when initializing an instance of a class , and then specifically start an instance of the class ImageProviderusing the method start()- we immediately start the chain of dependent operations during initialization and set waitUntilFinishedequal false, since this is not the Playgroundapplication, and we cannot use synchronous method wait():
ImageProviderwe have an initializer, at the input of which we have to submit a string imageURLStringwith the URL address of the image and the closure completion, which genericin a way returns an image like the UIImage?one who created this instance of the classImageProvider, instead of using a computed property image. The input closure completionhas a signature (UIImage?) -> (), that is, takes an image UIImage?and returns nothing. It can be used to return to UITableViewController.ImageProvider, which will lead to the destruction of all the started operations if the table cell leaves the screen before all operations are completed. Therefore, we have a method cancel()in class ImageProvider.ImageProviderprovides asynchronous execution of a group of dependent operations for loading an image from a server, filtering it and delivering it to a method UITableViewDelegate. If necessary, we can remove an instance of this class.ImageTableViewController.tableView( _ tableView:, cellForRowAt indexPath:), we will do it in another delegate method - tableView( _ tableView: , willDisplay cell:, forRowAt indexPath:)and then we will delete the images in the method tableView( _ tableView: , didEndDisplaying cell:, forRowAt indexPath:).extensionfor these two methods. Let's start with the method tableView( _ tableView: , willDisplay cell:, forRowAt indexPath:).
tableView( _ tableView:, cellForRowAt indexPath:), we will have a cell celland indexPath. First, we perform the standard procedure for verifying that the cell is of type ImageTableViewCell. Then we create imageProviderwith a string imageURLs [ (indexPath as NSIndexPath).row ]as the URL of the image address and a closure that is designed as a “tail” closure. In the closure, we get the image imagethat needs to be shown in this cell of the table. This UI, and we should use to update it.main queue, because if you try to do an update UIon the background queue ( background queue), then this will not work, and you will wonder why this image does not appear. We have a mainclass property OperationQueuefor main queue, and all we need to do is call a method updateImageViewWithImage( image )on main queuethat will update the cell we need UITableViewCell.imageProvider, otherwise we will not be able to find it later and delete the operations associated with it.ImageTableViewControllerand add a new property with the name imageProviders:
imageProvidersis a set of objects of the type imageProvider, which is initially empty.extensionclass ImageProvider, which confirms the protocol Hashablerequired for the sets Set:
hashValueand the comparison operator for equality ==. And now we can set and compare instances of objects ImageProvider. Go back to ImageTableViewController. Now we can track instances of objects ImageProviderand add them to the set imageProvidersthat are currently involved:
tableView, which is called just before the cell appears on the screen. At this point, we create ImageProviderthat asynchronously loads, filters the image and returns the resulting image imageto completionHandler. We use imageto update UIImageViewon main queue. Then we memorize ImageProviderin the set imageProvidersso that we can destroy it later. We will do this in the following delegate method tableViewwith a name tableView( _ tableView:, didEndDisplaying cell:, forRowAt indexPath:)that is called immediately after the cell leaves the screen. It is here that we need to destroy all the operations of this ImageProvider:
ImageProviderfor this cell celland use the method of cancel ()thisImageProvider, which removes all operations of this provider, and then we delete the provider from my set imageProviders. As always, we first perform the standard procedure for verifying that the cell has a type ImageTableViewCell, and then we find all providers that have the same row ImageURLStringas for the given cell. We go through all these providers and remove them, and then remove them from the set imageProviders. This is all we need to do.
OperationTableViewControlleron Github .Operation OperationQueue GCDGCDand Operationshave a lot of similar features, but the table shows their differences.
DispatchGroupand OperationQueuecan handle the event associated with the complete completion of all tasks, but you must be very careful when running the method waitUntilAllOperationsAreFinishedfor the queue OperationQueue, which in no case MUST NOT be main queue.dependecies), then all you can do GCDis implement a chain of tasks for privatesequential ( serial) DispatchQueue. But this is the strongest side OperationQueue. Dependencies ( dependecies) on OperationQueuecan be more complex than just chains, and operations can be performed on different queues OperationQueue.GCDto solve the problem of "writers" and "readers", if the sequential ( serial) line is DispatchQueuenot suitable. The appropriate solution to this problem is OperationQueuevery confusing and requires flagsvery special dependencies.GCDyou can delete only DispatchWorkItems. Operations Operationscan be deleted using their own method cancel()or all operations immediately on OperationQueue. You can delete the circuit in BlockOperation.GCDand Operationsperform a SYNCHRONOUS function ASYNCHRONIC. In doing so, the operation Operationsupplies us with an object-oriented model for encapsulating all the data for this reusable function, including the implementationsubclasses Operation. But often, for simpler tasks that are not burdened with complex dependencies, it is more convenient to use lighter methods GCDthan to create an operation Operation. In addition, the Dispatchblocks GCDtake less time to complete: from nanoseconds to milliseconds, and the operation Operationusually takes from a few milliseconds to minutes.Operationqueue of operations OperationQueue:
Operationcan encapsulate a task and data in one object that has a “life cycle” and properties that reflect its states.BlockOperationis an object-oriented "wrapper" around DispatchQueue.global(), which allows you to track the execution of a closure group instead of losing control of this group on DispatchQueue.global(). BlockOperationIt is convenient to use for simple operations as an alternative GCDif you are already using your application Operationsand do not want to interfere with them DispatchQueue.Operationreveal their main potential if they are launched onOperationQueue. Once you have prepared an operation Operation, you transfer it to OperationQueue, which controls the order of execution of all operations, essentially being a very simple model that hides the complexity of multi-threaded programming. OperationQueuesimilar to DispatchGroup, for which you can mix operations with different levels qualityOfServiceand wait until all operations end. But you must be very careful when you call this syncmethod.Operation, we must do something special in order to accurately record its completion. We must manage the ASYNCHRONOUS operation AsyncOperationmanually using KVO .Operationis that you can combine them into chains of operations to produce a complex dependency graph ( dependencies). This means that you can very easily define an operation Operationthat cannot start until one or more other operations Operationhave completed. The article shows how a protocol can be used protocolto transfer data between operations Operationfor a dependency graph ( dependencies). But you must examine your dependency graph in order to avoid cycles that can cause deadLock(intractable deadlock), especially if there are dependencies between operations on different ones OperationQueue.Operationto the queueOperationQueue, you have lost control over this operation, because now the queue OperationQueueitself is the schedule for launching operations for execution and manages their implementation. However, you can use the method cancel ()in order to prevent the operation from starting. The article shows how to take property into account isCancelledwhen constructing an operation Operationand how you can delete absolutely all operations on the operation queue OperationQueue.main queue. In this application, we used a wide range of techniques for working with an operation Operation, in particular, with ASYNCHRONOUS OPERATION AsyncOperationand its dependencies ( dependencies), which allowed us to achieve a significant improvement in our own UI.Swift 3and 4on iOS that currently exists. Now you can take a full part in the discussion of future multithreaded processing capabilities in Swift, which is being laid right now, when for the version, multi-threading ( ) is declared a Swift 5priority direction in addition to ABIstability concurrency. VersionSwift 5It assumes only the beginning of work on a completely new multithreading model, the implementation of which will continue in subsequent versions. There are already proposals for a future multithreading model in Swift 5 . So “turn on the motors!” And forward.Source: https://habr.com/ru/post/335756/
All Articles