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:Operation
is 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 otherOperations
and control this operation.
Operation
Operation
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.OperationQueue
OperationQueue
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 State
to represent the state of the asynchronous operation its own variants: ready
, executing
, finished
.State
also contains a fileprivate
property with a name keyPath
, which we will use as a switch for KVO notifications. The property keyPath
is computed and is made up of a string "is"
connected to rawValue
, which is the name of the enumeration element State
with a capital letter.AsyncOperaton
variable 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 state
State
ready
state
willSet {}
didSet {}
state
state
, for example, from executing
to finished
, we need to send KVO notifications willChangeValue
about the upcoming change of both operations: the new state newValue
( finished
) and the current state state
( executing
). After the state switch state
happens, we send KVO notifications didChangeValue
for keyPath
both states: oldValue
( executing
) and state
( finished
).isReady
, isExecuting
, isFinished
. Therefore, the asynchronous operation AsyncOperation
we need to redefine the "native" state variables operations isReady
, isExecuting
,isFinished
using the new property state
as calculated variables that return the correct values. We will do this in a extension
class extension AsyncOperation
:isReady
becomes equal true
, and we must use the property isReady
for superclass
, which “perceives” dependencies ( dependencies
) on other operations. By combining our own logic with property isReady
for superclass
, we can be sure that the operation is really “ready.” Note that if a variable state
is equal .finished
, then the property superclass
isFinished
is equal true
and the property isExecuting
is false
. We will also override the property isAsynchronous
by returning true
, and two methods: start()
and cancell()
.start()
we check whether the operation is destroyed, that is, the property isCancelled
is 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 stat
to 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:AsyncOperation
as superclass
, we have to redefine main ()
and remember to set the operation state state
in .finished
to callback
:callback
returns the result result
that we assign to the operation property self.result
and set the operation state state
to .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.SumOperation
to get an array of the sum of pairs of numbers:SumOperation
and place it in the operation queue in the additionQueue
usual 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.URL
by 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 state
in .finished
to completion
:operationLoad
, get the image operationLoad.outputImage
and map to view
:AsyncOperations.playground
on Github .dependencies
)OperationQueue
will know that the “filter” is set to the state ready
only 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 OperationQueue
implementation to automatically launch operations as you need. API
class Operation
that supports work with "dependencies" (dependencies
) - very simple, but reveals a fantastic power when working.dependency
), as well as get a list of “dependencies” dependencies
added for this operation. Below we will use the list dependencies
to get the input image of the dependent filtering operation.ImagePass
that will supply us with the necessary data, in our caseUIImage?
:ImageLoadOperation
image loading operation already known to us from the network. At the input, the URL
image is set as a string urlString
, and the output is the image itself outputImage
.ImageLoadOperation
" confirms "the protocol ImagePass
and returns the image
output loaded image as a protocol property outputImage
.FilterOperation
- in the absence of an input image, it _inputImage
analyzes its" dependencies " dependencies
and is interested only in those that" confirmed "the protocol ImagePass
. He chooses the first such dependent operation and extracts his own inputImage
: From theinputImage
, 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()
.inputImage
output image of the “load” operation as the input image . To do this, we initialize the filtering operation filter
with the value nil
:LoadAndFilter.playground
on Github .OperationQueue
Cancellation.playground
on Github .OperationQueue
opportunity - the ability to destroy operations.Operation
in 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 Operation
using the method cancel()
.cancel()
will instantly stop the operation, but it is not. The method cancel()
only sets the isCancelled
operation 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 isFinished
to 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 isCancelled
set 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 isCancelled
to determine whether the operation has already been destroyed. And if the operation is destroyed and it shows the value of the true
property isCancelled
, then you have to carry out the necessary actions to stop the operation. If operationOperation
performed 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.isFinished
equal state true
.API
class Operation
, designed to remove the operation is very simple and consists of only two positions:cancel()
— it sets the property of the isCancelled
operation to true
. It is important to note that when a method is called cancel()
, the properties isExecuting
and isFinished
also change to false
and, true
respectively.isCancelled = true
) and will not end ( isFinished = true
). The property isCancelled
informs the operation that it should stop, and the property isFinished
informs the system that the operation has already been stopped.AsyncOperation
redefines the method cancel()
in such a way that it sets the state of the operation state
to .finished
, and this change leads to a change in the properties of the operation isFinished
and isExecuting
:OperationQueue
can destroy all operations:cancel()
.Cancellation.playground
on Github .ArraySumOperation
has an input array of inputArray
tuples consisting of a pair of integers and forms an array of outputArray
their output totals:slowAdd
presented in the folder Source
on the Cancellation.playground and add to the output array outputArray
.sumOperation
, add it to the operation queuequeue
and start the timer, which will allow us to further regulate the time after which we will be able to check the operation response sumOperation
to the method call cancel()
. In addition, the operation has completionBlock
, in which we stop the timer, show on Playground
outputArray
and complete the work on Playground
:sumOperation
takes a little more than 5 seconds. Now we will try to destroy this operation, 2 seconds after the start, calling the method cancel ()
:sumOperation
completed, no operation was destroyed. What is the matter?But the fact is that the method cancel ()
only sets the property isCancelled
to 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 isCancelled
set 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 slowAddArray
to 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:slowAddArray
array of input
pairs of integers, in addition, it has an argument progress
, which is a Optional
function that takes a variable depth of array processingDouble(results.count) / Double(input.count
Bool
. This Bool
defines the continuation of array processing.main()
operations AnotherArraySumOperation
(the previous figure), we passed an slowAddArray
array to the function inputArray
, and the argument was progress
formatted as a “tail” closure, in which we used the property progress
for printing. The property progress
is 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
.SumOperation
with a new operation AnotherArraySumOperation
:isCancelled
cancel ()
OprationQueue
cancellAllOperations
. 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.playground
on Github .ArraySumOperation
presented 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 SumOperation
adds two numbers from the input pair inputPair
using the slow addition function slowAdd()
and returns the result output
:GroupAdd
that manages the private
queue of operations queue
and a variety of operationsSumOperation
in order to calculate the sum of all pairs in the input array and place in the output array outputArray
tuples ( Int, Int, Int )
consisting of input data and result:GroupAdd
input array of input
pairs of numbers is formed from which type operations are formed SumOperation
. In completionBlock
each operation, the result is added to the output array outputArray
, which is performed on a single private
serial line operations appendOperation
to avoid race condition .Operation
methods: 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.playground
on Github .CancellationFourImages.playground
on Github .UI
. Let's try to arrange this sequence into a separate class ImageProvider
that will manage these operations on OperationQueue
using the methods start ()
, wait ()
and cancel ()
.main()
). One is the asynchronous operation already familiar to us AsyncOperation
, and the other is the operation of ImageTakeOperation
extracting the input image inputImage
from the dependencies dependecies
.AsyncOperation
create an image download operation from the “network” at the specified URL address:ImagePass
for transmitting the resulting image outputImage further along the chain of operations.ImageTakeOperation
extracts the input image inputImage
from 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 ImagePass
used to transfer images in a chain of sequential operations: TheImageTakeOperation
very convenient to use as an superclass
operation. participating in a chain of dependent operations. For example, for filtering operation Filter
:PostProcessImageOperation
:ImageOutputOperation
:ImageProvider
. Create the regular class ImageProvider
that controls the private
queue operations operationQueue
, and the sequence of operations dataLoad
, filter
and output
to load an image from a given the URL , filter it and pass in the circuit completion
:ImageProvider
has all the typical operation Operation
methods: 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.playground
on Github .OperationTableViewController
on 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:ImageTableViewController
that serves the screen fragment looks like Image Table View Controller
: TheImageTableViewController
is an array of 8 URLs :ImageTableViewCell
for the cell of the table into which the image is loaded has the form:Public API
this class is a string imageURLString
containing the URL address of the image. But if we set imageURLString
not 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 spinner
that starts if you assign a imageURLString
value in the method tableView( _ : cellForRowAt:)
.UITableViewDelegate
responsible for the interaction of the cell UITableViewCell
with the table.UITableView
:tableView( _ : cellForRowAt:)
,tableView( _ : willDisplay:forRowAt:)
,tableView( _ : didEndDisplaying:forRowAt:)
,ImageProvider
will 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.ImageProvider
that will be used in this application. Unlike the class variant ImageProvider
that was used in the previous section onPlayground
, we will use its simplified form. Namely, we do not need to ImageProvider
stop ( isSuspended = true
) the operation queue when initializing an instance of a class , and then specifically start an instance of the class ImageProvider
using the method start()
- we immediately start the chain of dependent operations during initialization and set waitUntilFinished
equal false
, since this is not the Playground
application, and we cannot use synchronous method wait()
:ImageProvider
we have an initializer, at the input of which we have to submit a string imageURLString
with the URL address of the image and the closure completion
, which generic
in 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 completion
has 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
.ImageProvider
provides 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:)
.extension
for these two methods. Let's start with the method tableView( _ tableView: , willDisplay cell:, forRowAt indexPath:)
.tableView( _ tableView:, cellForRowAt indexPath:)
, we will have a cell cell
and indexPath
. First, we perform the standard procedure for verifying that the cell is of type ImageTableViewCell
. Then we create imageProvider
with 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 image
that 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 UI
on the background queue ( background queue
), then this will not work, and you will wonder why this image does not appear. We have a main
class property OperationQueue
for main queue
, and all we need to do is call a method updateImageViewWithImage( image )
on main queue
that 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.ImageTableViewController
and add a new property with the name imageProviders
:imageProviders
is a set of objects of the type imageProvider
, which is initially empty.extension
class ImageProvider
, which confirms the protocol Hashable
required for the sets Set
:hashValue
and 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 ImageProvider
and add them to the set imageProviders
that are currently involved:tableView
, which is called just before the cell appears on the screen. At this point, we create ImageProvider
that asynchronously loads, filters the image and returns the resulting image image
to completionHandler
. We use image
to update UIImageView
on main queue
. Then we memorize ImageProvider
in the set imageProviders
so that we can destroy it later. We will do this in the following delegate method tableView
with 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
:ImageProvider
for this cell cell
and 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 ImageURLString
as 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.OperationTableViewController
on Github .Operation
OperationQueue
GCD
GCD
and Operations
have a lot of similar features, but the table shows their differences.DispatchGroup
and OperationQueue
can handle the event associated with the complete completion of all tasks, but you must be very careful when running the method waitUntilAllOperationsAreFinished
for the queue OperationQueue
, which in no case MUST NOT be main queue
.dependecies
), then all you can do GCD
is implement a chain of tasks for private
sequential ( serial
) DispatchQueue
. But this is the strongest side OperationQueue
. Dependencies ( dependecies
) on OperationQueue
can be more complex than just chains, and operations can be performed on different queues OperationQueue
.GCD
to solve the problem of "writers" and "readers", if the sequential ( serial
) line is DispatchQueue
not suitable. The appropriate solution to this problem is OperationQueue
very confusing and requires flags
very special dependencies.GCD
you can delete only DispatchWorkItems
. Operations Operations
can be deleted using their own method cancel()
or all operations immediately on OperationQueue
. You can delete the circuit in BlockOperation
.GCD
and Operations
perform a SYNCHRONOUS function ASYNCHRONIC. In doing so, the operation Operation
supplies 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 GCD
than to create an operation Operation
. In addition, the Dispatch
blocks GCD
take less time to complete: from nanoseconds to milliseconds, and the operation Operation
usually takes from a few milliseconds to minutes.Operation
queue of operations OperationQueue
:Operation
can encapsulate a task and data in one object that has a “life cycle” and properties that reflect its states.BlockOperation
is 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()
. BlockOperation
It is convenient to use for simple operations as an alternative GCD
if you are already using your application Operations
and do not want to interfere with them DispatchQueue
.Operation
reveal 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. OperationQueue
similar to DispatchGroup
, for which you can mix operations with different levels qualityOfService
and wait until all operations end. But you must be very careful when you call this sync
method.Operation
, we must do something special in order to accurately record its completion. We must manage the ASYNCHRONOUS operation AsyncOperation
manually using KVO .Operation
is 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 Operation
that cannot start until one or more other operations Operation
have completed. The article shows how a protocol can be used protocol
to transfer data between operations Operation
for 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
.Operation
to the queueOperationQueue
, you have lost control over this operation, because now the queue OperationQueue
itself 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 isCancelled
when constructing an operation Operation
and 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 AsyncOperation
and its dependencies ( dependencies
), which allowed us to achieve a significant improvement in our own UI
.Swift 3
and 4
on 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 5
priority direction in addition to ABI
stability concurrency
. VersionSwift 5
It 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