Hours of light
My blog

1. Why are all UI operations in the main thread

Not only iOS systems, including Android, all UI rendering and operations are done in the main thread. So why not use multithreading? Using multiple threads to render the UI is faster and smoother. However, for system designers and developers, the cost of solving threading problems is much higher, meaning that the costs far outweigh the benefits. So the engineers put all the UI rendering and manipulation into the main thread. See why the UI must be operated on the main thread.

2. Why multithreading

Given that all UI operations are single-threaded, why multithreading? In App development, there are not only UI operations, but also some other time-consuming operations, such as network requests, file reading operations, AR model download, etc. At this point, the developer needs to put time-consuming operations into the child thread, and then return to the thread to perform some UI operations.As shown in the figure above, when I entered the Controller, I started to download pictures. Since it took several seconds or even more than ten seconds to download pictures, I synchronized sliding the one at the bottomUISliderAt this time,UISliderIt does not swipe until all images have been downloaded.

Just imagine if each App we use needs to wait for a long time before users can operate when time-consuming operation, which is bound to give users a very bad experience. The solution at this point is to create a new thread to download the image without affecting the user’s UI operations or image download.

3, the implementation of multithreading

Apple offers developers three ways to implement multithreading:

(1), the Thread

  • lightweight
  • It requires the developer to manually manage the thread lifecycle and thread synchronization

(2) GCD (Grand Central Dispatch)

  • The relativeThreadThere is no need to manage the thread life cycle and operations are simpler
  • It maintains a thread pool and automatically manages threads dynamically according to the current situation of the mobile system, without requiring developers to manage the thread pool and thread concurrency
  • The underlying source code is Open source. Click on Apple Open Spurce to see the source code

(3)、 Cocoa Operation

  • Object-oriented APIS
  • You can cancel, rely on, prioritize tasks, and subclass

4, multithreading common queue

Multithreading can be divided into three types of queues according to the queue mode of task execution:

  • Main queue: Tasks executed in the main thread
  • Serial Queue: Tasks are executed in sequence. Only one task is executed at a time
  • Concurrent Queue: Multiple tasks are executed simultaneously in an uncertain order

See Apple Developer About Dispatch Queues

5, Serial Queue

  • (1) Serial queues process concurrent tasks

As mentioned earlier, for time-consuming operations, it is common to put them into a child thread and then return to the main thread to refresh the UI. The core code is as follows:

DownloaderManager 'public class DownloaderManager: NSObject {public class func downloadImageWithURL(_ url: String) -> UIImage? { guard let data = try? Data(contentsOf: URL(string: url)!) Else {return nil} return UIImage(data: data)}} let serialQueue = DispatchQueue(label: "TSN.RPChat.io") for (index, Imgurl) in DownloaderManager. ImageArray. Enumerated () {/ / the download task serialQueue loaded into the queue. The async {let image = DownloaderManager.downloadImageWithURL(imgurl) DispatchQueue.main.async { girlsImg.image = image } } }Copy the code

When I run the code again, I find that I can swipe the UISlider while downloading the images and see that the images are loaded from top to bottom. By default, a serial queue is created so that the first image is downloaded before the second one is downloaded. For me at this point, my goal is to download and display all the images, and I don’t care about the order in which the images are downloaded or loaded. So I need to set the queue as a parallel queue when I create it.

6, Concurrent Queue

As a queue, tasks in a Concurrent queue are started in the order in which they are queued, but do not wait for the completion of previous tasks. IOS starts multiple threads to execute tasks in the queue in parallel based on the current system conditions.

Setting the Attributes attribute to Concurrent when creating the queue creates a parallel queue.

let concurrentQueue = DispatchQueue(label: "TSN.RPChat.io", attributes: .concurrent)
Copy the code

Run the project at this time, you can see that the images are not loaded in order, indicating the sameconcurrent queueAll tasks are executed in parallel.

7. Object-oriented Cocoa Operation

I created the queue above using GCD (Grand Central Dispatch), although GCD encapsulates thread management and adds object-oriented management mode. However, if I want to do more on a task in a queue, such as (view status, cancel tasks, control task execution order, etc.), it is still not convenient. With these issues in mind, Apple provides an object-oriented approach to the development of Operation, a multitasking execution mechanism. Operation is a GCD-based object encapsulation.

(1) Operation overview

Some use states of Operation:

  • **isReady** Is executable. It is generally used in asynchronous cases
  • 支那isExexuting* * markOperationWhether it is being executed
  • 支那isFinished* * markOperationWhether the execution has been completed, usually used asynchronously
  • 支那isCancelled* * markOperationWhether they have beencancelthe

For more details, see Apple Developer: Maintaining Operation Object States

(2), OperationQueue

  • OperationQueueYou can add multipleOperation
let ope1 = Operation()
let ope2 = Operation()
       
let que = OperationQueue()
que.addOperation(ope1)
que.addOperation(ope2)
Copy the code
  • maxConcurrentOperationCountMaximum Number of concurrent requests Current value. By default, the system dynamically determines the maximum number of concurrent requests
que.maxConcurrentOperationCount = 5
Copy the code

It is important to note that the maximum concurrency is not the number of threads, but the maximum number of concurrent tasks (or threads) that can be executed on the current queue at the same time.

  • Cancelable allOperation, but is currently being executedOperationNot be canceled
  • All of theOperationExit from destruction after execution

(3), BlockOperation

let queblock = BlockOperation.init(block: { [weak self] in
            
})
let que = OperationQueue.init()
que.maxConcurrentOperationCount = 3
que.addOperation(queblock)
Copy the code

(4), completionBlock

A callback when a task is completed. We can first create an Operation object and then add it to the queue by creating the Operation method, so that we can be notified when the task is complete by setting the completionBlock.

(5) Default priority

Apple provides priority for Operation, which controls its priority through the **qualityOfService** attribute.

public enum QualityOfService : Int {
    
    case userInteractive = 33

    case userInitiated = 25

    case utility = 17

    case background = 9

    case `default` = -1
}
Copy the code
  • UserInteractive: Highest priority, used for user interaction events
  • UserInitiated: Indicates the second highest priority for events that the user needs to execute immediately
  • Utility: Common priority, used for common tasks
  • Background: The lowest priority, used for unimportant tasks
  • Default: indicates the default priority. This priority is used by the main thread and threads that have no priority
operation.qualityOfService = .default
Copy the code

The priority in the OperationQueue is controlled by the **queuePriority** property:

public enum QueuePriority : Int {

        case veryLow = -8

        case low = -4

        case normal = 0

        case high = 4

        case veryHigh = 8
    }
Copy the code
operation.queuePriority = .high
Copy the code

(6) Use OperationQueue to download images

Let queue = OperationQueue() // Create an Operation queue.addOperation {et image = DownloaderManager.downloadImageWithURL(imgurl) OperationQueue.main.addOperation { girlsImg.image = image } }Copy the code

The important thing to note here is that the code for updating the UI is done in the main thread. Get the main thread queue with operationQueue. main and add addOperation to put the UI update task on the main thread. If you need to do something about it when the download is complete, you can use completionBlock,

let operation = BlockOperation(block: {/ / to perform tasks, such as download images let image = DownloaderManager. DownloadImageWithURL (imgurl) / / when the download is complete, Return to the main thread rendered image OperationQueue. Main. AddOperation {girlsImg. Image = image}}) operation.com pletionBlock = {/ / execution is completed} / / Set the maximum number of concurrent does not set the system back to dynamically set the maximum number of concurrent according to the current situation is set to 1 for serial queue queue. MaxConcurrentOperationCount = 5 / / add Operation to the queue in the queue queue.addOperation(operation)Copy the code

(7) Set the correlation between tasks

As shown in the figure, when the Download button is clicked, pictures are downloaded, but the customer requires loading in 432 order, but picture 1 is not affected, which is needed hereaddDependencyMethod, let the images download in order of 432, picture 1 download in parallel, the core code is as follows:

let queue = OperationQueue()

let operation1 = BlockOperation(block: {
       let image = DownloaderManager.downloadImageWithURL(imgArray[0])
       OperationQueue.main.addOperation {
           self.girlsImg1.image = image
    }
})
operation1.completionBlock = {
    print("-------operation1")
 }
        
let operation2 = BlockOperation(block: {
        let image = DownloaderManager.downloadImageWithURL(imgArray[1])
        OperationQueue.main.addOperation {
           self.girlsImg2.image = image
     }
})
operation2.completionBlock = {
     print("-------operation2")
}
        
let operation3 = BlockOperation(block: {
    let image = DownloaderManager.downloadImageWithURL(imgArray[2])
        OperationQueue.main.addOperation {
          self.girlsImg3.image = image
    }
})
operation3.completionBlock = {
   print("-------operation3")
}
        
let operation4 = BlockOperation(block: {
    let image = DownloaderManager.downloadImageWithURL(imgArray[7])
    OperationQueue.main.addOperation {
         self.girlsImg4.image = image
    }
})
operation4.completionBlock = {
    print("-------operation4")
}

operation3.addDependency(operation4)
operation2.addDependency(operation3)
        
queue.addOperation(operation1)
queue.addOperation(operation4)
queue.addOperation(operation3)
queue.addOperation(operation2)
Copy the code

Add Operation to Queue after addDependency to ensure proper dependencies before execution. Running the code several times, you can see that the image download order is still 4->3->2.

-------operation1
-------operation4
-------operation3
-------operation2
Copy the code

(8) Cancelled tasks

In addition to setting the association of tasks in a queue, you can also control the cancellation of tasks in the queue, but the cancellation results vary depending on the status of the task:

  • Cancelling completed tasks does not affect their results
  • When a task is cancelled, all tasks associated with it are also cancelled
  • After the mission was canceled,completionBlockWill still be executed

As shown in the picture, when I click the Download button and quickly click the Cancel button, I can see that the download task of picture 1 has been cancelled.

let cancelItem = UIBarButtonItem()
cancelItem.title = "cancel"
cancelItem.rx.tap.subscribe(onNext: {
   self.queue.cancelAllOperations()
}).disposed(by: disposeBag)
Copy the code

The isCancelled property of Operation can be used to determine whether the task isCancelled. When isCancelled returns true, the task has been cancelled.

-------operation4,false
-------operation3,false
-------operation2,false
-------operation1,true
Copy the code

This article mainly introduces some simple applications of Operation. The correct understanding and application of these multi-threading technologies is the foundation of building complex App. For more multi-threading applications, please refer to the official multi-threading documentation:

Apple Developer About Dispatch Queues

Apple Developer Threading Programming Guide

In this paper, the demo