original

# 1. Brief introduction

GCD provides a simple API to create serial and parallel queues to perform background tasks without the developer to manage threads

GCD abstracts the allocation of threads for calculation into a dispatch queue. Developers only need to create their own dispatch queue, or they can use the built-in global dispatch queue provided by Apple, which contains several built-in

Quality of Service (QoS)
,include
interactive
,
user initiated
,
utility
, and
background

• DispatchGroup

In some cases, as a developer, you need to batch asynchronous tasks in the background, and then be notified when all tasks are completed in the future. Apple provides the DispatchGroup class to perform this operation.

The following is an Apple pair

DispatchGroup
A brief summary.

Groups allow you to aggregate a set of tasks and synchronize behaviors on the group. You attach multiple work items to a group and schedule them for asynchronous execution on the same queue or different queues. When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing.

Groups allow a group of tasks to be aggregated synchronously. You can add work items to the group and arrange them to be executed asynchronously on the same or different queues. When all the work items are executed, the group will execute the completion handler. You can also wait for all tasks in the group to complete execution synchronously

DispatchGroup
It can also be used to synchronously wait for all tasks to complete execution, but we will not do this in this tutorial.

• Semaphore

Here are a few scenarios that developers may encounter

1. Multiple network requests need to wait for other requests to complete before continuing

2. Perform multiple video/image processing tasks in the background

# 2. What are we going to do

We will create a simple project to simulate the background synchronization download, and explore how to use DispatchGroup and DispatchSemaphore. When all download tasks are successfully completed, we will display a successful dialog box in the UI. It also has a variety of functions, such as:

3. Set the number of concurrent tasks that can run a queue at the same time

# 3. Initial project

The initial project has already created the corresponding UI interface, so we can focus on how to use it

dispatch group & dispatch semaphore
.

We will use

dispatch group
dispatch semaphores
Simulate the number of files downloaded at the same time is limited to the specified number

pending

enum  TaskState  {
case pending
case inProgress( Int )
case completed
}
Copy code
2. An initialization method accepts identifier

And a status update closure callback parameter

///identifier task identifier is used to distinguish other tasks
///stateUpdateHandler closure callback is used to update task state at any time
copying the code
3. progress

The method is temporarily empty, we will add it later
DispatchGroup
with
semaphore

5. startSleep

# V. Introduction to View Controller

JobListViewController
Contains two
table view
, And several
sliders

var downloadTableView: UItableView  //Display download tasks
var completedTableView: UITableView  //Display completed tasks
var randomizeTimeSwitch: UISwitch  //Whether to take a random time open then random 1 to 3 seconds, otherwise the default one second
to copy the code

The specific composition of the class:

table view

table view

var downlaodTasks = [ DownloadTask ] [] { didSet {downloadTableView.reloadData ()}}
Copy the code
1. SimulationOption

struct  SimulationOption  {
}
Copy code
2. TableViewDataSource
cellForRowAtIndexPath
Reuse in method
progressCell
, By passing
To configure different states
cell

Decided we want to be in
dispatch group

Decided on
dispatch group

For example, if there are 100 download tasks, we only hope that there can only be 10 downloads in the queue at the same time, then it can be used

DispatchSemaphore
To limit this maximum

5. randomizeTimeSwitch
Whether to take random time

# 6. Create DispatchQueue, DispatchGroup, & DispatchSemaphore

Now start to simulate when the user clicks

start
Button operation, this operation will trigger the currently empty
startOperation
method,

use

DispatchQueue, DispatchGroup, DispatchSemaphore
Create three variables for each class

DispatchQueue
Initialize given a unique identifier, usually represented by reverse domain dns (reverse domain dns)

Then set

attributes
for
concurrent
, So that multiple tasks can be asynchronously paralleled.

DispatchSemaphore
Initialize settings
value
for

Finally, when you click

start
After the button, all interactions, including buttons, sliders, and switches, are set to be non-clickable

@objc  func  startOperation () {

randomizeTimeSwitch.isEnabled =  false

let dispatchQueue =  DispatchQueue (label: "com.alfianlosari.test" , qos: .userInitiated, attributes: .concurrent)
let dispatchGroup =  DispatchGroup ()
let dispatchSemaphore =  DispatchSemaphore (value: option.maxAsyncTasks)
}
Copy code

Next, we based

option
Attributive
maximumJob
To create the corresponding number of tasks.

Given an identifier to initialize

, And then pass in the closure of task status update
callback

callback
The specific implementation is as follows

1. According to the task s identifier, from
Find the index corresponding to the task in the array
2. completed
Status, we only need to change the task from
Remove it, and then insert the task into
The position where the index of the array is 0,
with
All have an attribute observation, once changed, their respective
tabe view
Will trigger
3. inProgress
Status in
Pass in
cellForIndexPath:
Method to find the corresponding
ProgressCell
,transfer
configure
Method, pass the new state, and eventually, we will call
tableView
of
Method to prevent the height of the cell from changing
@objc func startOperation() {
//...
let identifier = "\(i)"
DispatchQueue.main.async { [unowned self] in
return
}

case .completed:
case .pending,.inProgress(_):
guard let cell = self.downloadTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? ProgressCell else {
return
}
}
}
})
}
)
}
Copy code

Next, we assign the task to

DispatchQueue
with
DispatchGroup
startOperation
In the method,

We will iterate through all

, Call each
of
Method and put
dispatchGroup, dispatchQueue, dispatchSemaphore
Passed as a parameter, and at the same time
option
inner
randomizeTimer

in

In the method, pass
dispatch group
To
dispatchQueue async
In the method, in the closure we will do the following:

1. transfer
group
of
enter
Method to indicate that our task execution has entered the reform
group
, When the task is over, you also need to call
leave
method
2. We also need to trigger
semaphore
of
wait
Method to reduce the semaphore count. When the task is over, you also need to call
semaphore
of
signal
Method to increase the semaphore count so that it can perform other tasks
3. In the middle of the call of the previous method, we pass at a specific time
inProress
Until it is set to
complete
4. Whenever the state is updated, Swift's property observer will call
Closure and pass
@objc  func  startOperation () {
//...
\$0 .startTask(queue: dispatchQueue, group: dispatchGroup, semaphore: dispatchSemaphore, randomizeTime: self .option.isRandomizedTime)
}
}
Copy code
class  DownloadTask  {

var progress: Int  =  0
let identifier: Stirng
var state =  TaskState .pending {
didSet {
self .stateUpdateHandler( self )
}
}

self .identifier = identifier
self .stateUpdateHandler = stateUpdateHandler
}

func  startTask ( queue : DispatchQueue , group : DispatchGroup , semaphore : DispatchSemaphore , randomizeTime : Bool  =  true ) {
queue.async(group: group) {[ weak  self ] in
group.enter()
semaphore.wait()
self ? .State = .inProgress( 5 )
self ? .StartSleep(randomizeTime: randomizeTime)
self ? .State = .inProgress( 20 )
self ? .StartSleep(randomizeTime: randomizeTime)
self ? .State = .inProgess ( 40 )
self ? .StartSleep(randomizeTime: randomizeTime)
self ? .State = .inProgess( 60 )
self? .startSleep(randomizeTime: randomizeTime)
self ? .state = .inProgess( 80 )
self ? .startSleep(randomizeTime: randomizeTime)
self ? .state = .completed
group.leave()
semaphore.signal()
}
}

private  func  startSleep ( randomizeTime : Bool  =  true ) {
Thread .sleep(forTimeInterval: randomizeTime ?  Double ( Int .random(in: 1 ... 3 )): 1.0 )
}
}
Copy code

Finally, when all tasks are completed, you can pass

group notify
queue
Queue, and there is a callback, you can handle some tasks that need to be done in the callback

In the callback, we only need to pop up a completion message, and ensure that all buttons, sliders, and switches are restored to be clickable

@objc  func  startOperation () {
//...
dispatchGroup.notify(queue: .main  ) {[ unowned self ] in
self .navigationItem.rightBarButtonItem ? .isEnabled =  true
self .randomizeTimeSwitch.isEnabled =  true
}
}
Copy code

# X. Summary

The next version of Swift uses

async aswit
To perform asynchronous operations. But when we want to perform asynchronous operations in the background,
GCD
Still provides us with the best performance. use
DispatchGroup
with
DipatchSemaphore
, We can group multiple tasks together, execute tasks in the required queue, and get notified when all tasks are completed.

Apple also provides a higher level, abstract

OperationQueue