Prioritize Work with Quality of Service Classes
Apps and operations compete to use finite resources—CPU, memory, network interfaces, and so on. In order to remain responsive and efficient, the system needs to prioritize tasks and make intelligent decisions about when to execute them.
Work that directly impacts the user, such as UI updates, is extremely important and takes precedence over other work that may be occurring in the background. This higher priority work often uses more energy, as it may require substantial and immediate access to system resources.
As a developer, you can help the system prioritize more effectively by categorizing your app’s work, based on importance. Even if you’ve implemented other efficiency measures, such as deferring work until an optimal time, the system still needs to perform some level of prioritization. Therefore, it is still important to categorize the work your app performs.
About Quality of Service Classes
A quality of service (QoS) class allows you to categorize work to be performed by NSOperation
, NSOperationQueue
, NSThread
objects, dispatch queues, and pthreads (POSIX threads). By assigning a QoS to work, you indicate its importance, and the system prioritizes it and schedules it accordingly. For example, the system performs work initiated by a user sooner than background work that can be deferred until a more optimal time. In some cases, system resources may be reallocated away from the lower priority work and given to the higher priority work.
Because higher priority work is performed more quickly and with more resources than lower priority work, it typically requires more energy than lower priority work. Accurately specifying appropriate QoS classes for the work your app performs ensures that your app is responsive as well as energy efficient.
Choosing a Quality of Service Class
The system uses QoS information to adjust priorities such as scheduling, CPU and I/O throughput, and timer latency. As a result, the work performed maintains a balance between performance and energy efficiency.
When you assign a QoS to a task, consider how it affects the user and how it affects other work. As shown in Table 4-1, there are four primary QoS classes, each corresponding to a level of work importance.
QoS Class |
Type of work and focus of QoS |
Duration of work to be performed |
---|---|---|
User-interactive |
Work that is interacting with the user, such as operating on the main thread, refreshing the user interface, or performing animations. If the work doesn’t happen quickly, the user interface may appear frozen. Focuses on responsiveness and performance. |
Work is virtually instantaneous. |
User-initiated |
Work that the user has initiated and requires immediate results, such as opening a saved document or performing an action when the user clicks something in the user interface. The work is required in order to continue user interaction. Focuses on responsiveness and performance. |
Work is nearly instantaneous, such as a few seconds or less. |
Utility |
Work that may take some time to complete and doesn’t require an immediate result, such as downloading or importing data. Utility tasks typically have a progress bar that is visible to the user. Focuses on providing a balance between responsiveness, performance, and energy efficiency. |
Work takes a few seconds to a few minutes. |
Background |
Work that operates in the background and isn’t visible to the user, such as indexing, synchronizing, and backups. Focuses on energy efficiency. |
Work takes significant time, such as minutes or hours. |
Special Quality of Service Classes
In addition to the primary QoS classes, there are two special types of QoS (described in Table 4-2). In most cases, you won’t be exposed to these classes, but there is still value in knowing they exist.
QoS Class |
Description |
---|---|
Default |
The priority level of this QoS falls between user-initiated and utility. This QoS is not intended to be used by developers to classify work. Work that has no QoS information assigned is treated as default, and the GCD global queue runs at this level. |
Unspecified |
This represents the absence of QoS information and cues the system that an environmental QoS should be inferred. Threads can have an unspecified QoS if they use legacy APIs that may opt the thread out of QoS. |
Specify a QoS for Operations and Queues
If your app uses operations and queues to perform work, you can specify a QoS for that work. NSOperation
and NSOperationQueue
both possess a qualityOfService
property, of type NSQualityOfService
, which can be set to one of the following values:
NSQualityOfServiceUserInteractive
NSQualityOfServiceUserInitiated
NSQualityOfServiceUtility
NSQualityOfServiceBackground
Listing 4-1 shows how to set the QoS for an operation.
Objective-C
NSOperation *myOperation = [[NSOperation alloc] init];
myOperation.qualityOfService = NSQualityOfServiceUtility;
Swift
let myOperation: NSOperation = MyOperation()
myOperation.qualityOfService = .Utility
Quality of Service Inference and Promotion
Note that QoS is not a static setting for operations and queues, and could fluctuate over time depending on a variety of criteria. For example, situations may occur where the QoS of an operation and the QoS of a queue don’t match, an operation and a dependent operation don’t match, or an operation has no QoS assigned. In these scenarios, a QoS may be inferred.
Numerous rules govern how QoS inference and promotion occurs with regard to queues (see Table 4-3) and operations (see Table 4-4).
Situation |
Result |
---|---|
A queue has no QoS assigned and an operation with a QoS is added to the queue. |
The queue and its other operations, if any, remain unaffected. |
A queue has a QoS assigned, and an operation with a QoS is added to the queue. |
The QoS of the queue is promoted if the QoS of the new operation is higher. Any of the queue’s operations with a lower QoS are also promoted. Any operations with a lower QoS that are added to the queue in the future will infer the higher QoS. |
The QoS of a queue is raised by changing the value of the queue’s |
Any of the queue’s operations with a lower QoS are promoted to the higher QoS. Any operations with a lower QoS that are added to the queue in the future will infer the higher QoS. |
The QoS of a queue is lowered by changing the value of the queue’s |
Any of the queue’s operations remain unaffected. Any operations that are added to the queue in the future will infer the lower QoS, unless they have a higher QoS assigned, in which case they will retain their assigned QoS level. |
Situation |
Result |
---|---|
An operation has no QoS assigned. |
The operation infers the QoS of the parent operation, queue,
In a situation where an operation is created on the main thread, a QoS of |
An operation with a QoS is added to a queue with a higher QoS. |
The QoS of the operation is promoted to match the QoS of the queue. |
The QoS of a queue containing an operation is promoted. |
The operation infers the new QoS of the queue if it is higher than the current QoS of the operation. |
Another operation becomes dependent (child) on the operation (parent). |
The parent operation infers the QoS of the child operation if that QoS is higher. |
The QoS of the operation is raised by changing the operation’s |
The operation infers the new QoS. Any child operations are promoted to the new QoS if it is higher. Other operations in the operation’s queue that are in front of the operation are promoted to the new QoS if it is higher. |
The QoS of the operation is lowered by changing the operation’s |
The operation infers the new QoS. Any child operations remain unaffected. The queue of the operation remains unaffected. |
Adjust the QoS of a Running Operation
Once an operation is running, you can change its QoS in one of the following ways:
Change the
qualityOfService
property of the operation. Note that doing this also changes the QoS of the thread that’s running the operation.Add a new operation with a higher QoS to the running operation’s queue. This will promote the QoS of the running operation to match the QoS of the operation.
Use
addDependency:
to add an operation with a higher QoS to the running operation as a dependent.Use
waitUntilFinished
orwaitUntilAllOperationsAreFinished
. This will promote the QoS of the running operation to match the QoS of the caller.
Specify a QoS for Dispatch Queues and Blocks
If your app uses GCD, QoS classes can be applied to dispatch queues and blocks.
Dispatch Queues
For dispatch queues, you can specify a QoS by calling dispatch_queue_attr_make_with_qos_class
when creating the queue. First, create a dispatch queue attribute for the QoS, and then provide that attribute when you create the queue, as shown in Listing 4-2.
Objective-C
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0);
dispatch_queue_t myQueue = dispatch_queue_create("com.YourApp.YourQueue", qosAttribute);
Swift
let qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0)
let myQueue = dispatch_queue_create("com.YourApp.YourQueue", qosAttribute)
Table 4-5 shows how GCD QoS classes map to Foundation QoS equivalents.
GCD QoS classes (defined in sys/qos.h) |
Corresponding Foundation QoS classes |
---|---|
|
|
|
|
|
|
|
|
QoS is an immutable attribute of a dispatch queue, and can’t be changed once the queue has been created. To retrieve the QoS that’s assigned to a dispatch queue, call dispatch_queue_get_qos_class
. See Listing 4-3.
Objective-C
qosClass = dispatch_queue_get_qos_class(myQueue, &relative);
Swift
let qosClass = dispatch_queue_get_qos_class(myQueue, &relative)
Global Concurrent Queues
In the past, GCD has provided high, default, low, and background global concurrent queues for prioritizing work. Corresponding QoS classes should now be used in place of these queues. Table 4-6 describes the mappings between these queues and their corresponding QoS classes.
Global queue |
Corresponding QoS class |
---|---|
Main thread |
User-interactive |
DISPATCH_QUEUE_PRIORITY_HIGH |
User-initiated |
DISPATCH_QUEUE_PRIORITY_DEFAULT |
Default |
DISPATCH_QUEUE_PRIORITY_LOW |
Utility |
DISPATCH_QUEUE_PRIORITY_BACKGROUND |
Background |
A global concurrent queue exists for each QoS class. To retrieve the global concurrent queue corresponding to a given QoS, call dispatch_get_global_queue
and pass it the desired QoS class. Listing 4-4, for example, retrieves the global concurrent queue for the utility QoS class.
Objective-C
utilityGlobalQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
Swift
utilityGlobalQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
Queues that don’t have a QoS assigned and don’t target a global concurrent queue infer a QoS class of unspecified.
Dispatch Blocks
The GCD block API allows QoS classes to be applied at the block level, such as when calling dispatch_async
, dispatch_sync
, dispatch_after
, dispatch_apply
, or dispatch_once
. You do this when you create the block, as shown in Listing 4-5.
Objective-C
dispatch_block_t myBlock;
myBlock = dispatch_block_create_with_qos_class(
0, QOS_CLASS_UTILITY, -8, ^{…});
dispatch_async(myQueue, myBlock);
Swift
let block = dispatch_block_create_with_qos_class(0, QOS_CLASS_UTILITY) {
...
}
dispatch_async(myQueue, myBlock)
Priority Inversions
When high-priority work becomes dependent on lower priority work, or it becomes the result of lower priority work, a priority inversion occurs. As a result, blocking, spinning, and polling may occur.
In the case of synchronous work, the system will try to resolve the priority inversion automatically by raising the QoS of the lower priority work for the duration of the inversion. This will occur in the following situations:
When
dispatch_sync()
anddispatch_wait()
are called for a block on a serial queue.When
pthread_mutex_lock()
is called while the mutex is held by a thread with lower QoS. In this situation, the thread holding the lock is raised to the QoS of the caller. However, this QoS promotion does not occur across multiple locks.
In the case of asynchronous work, the system will attempt to resolve the priority inversions occurring on a serial queue.
Specify a QoS for Threads
NSThread
possesses a qualityOfService
property, of type NSQualityOfService
. This class will not infer a QoS based on the context of its execution, so the value of this property may only be changed before the thread has started. Reading the qualityOfService
of a thread at any time provides its current value.
The Main Thread and the Current Thread
The main thread is automatically assigned a QoS based on its environment. In an app, the main thread runs at a QoS level of user-interactive. In an XPC service, the main thread runs at a QoS of default. To retrieve the QoS of the main thread, call the qos_class_main
function, as shown in Listing 4-6.
Objective-C
qosClass = qos_class_main();
Swift
let qosClass = qos_class_main()
To retrieve the QoS of the currently running thread, call the qos_class_self
function, as shown in Listing 4-7.
Objective-C
qosClass = qos_class_self();
Swift
let qosClass = qos_class_self()
pthreads
You can assign a QoS class when creating a pthread by using an attribute, as shown in Listing 4-8, which creates a utility pthread.
Objective-C
pthread_attr_t qosAttribute;
pthread_attr_init(&qosAttribute);
pthread_attr_set_qos_class_np(&qosAttribute, QOS_CLASS_UTILITY, 0);
pthread_create(&thread, &qosAttribute, f, NULL);
Swift
var thread = pthread_t()
var qosAttribute = pthread_attr_t()
pthread_attr_init(&qosAttribute)
pthread_attr_set_qos_class_np(&qosAttribute, QOS_CLASS_UTILITY, 0)
pthread_create(&thread, &qosAttribute, f, nil)
To change the QoS of a pthread, call pthread_set_qos_class_self_np
and pass it the new QoS to apply, as shown in Listing 4-9.
Objective-C
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND,0);
Swift
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0)
About CloudKit and Quality of Service
If your app uses the CloudKit framework, it’s worth noting that certain CloudKit classes implement custom QoS behavior by default.
CKOperation
is a subclass of theNSOperation
class. Although theNSOperation
class has a default QoS level ofNSQualityOfServiceBackground
,CKOperation
objects have a default QoS level ofNSQualityOfServiceUtility
. At this level, network requests are treated as discretionary when your app isn’t in use. On iPhones, discretionary operations are paused when Low Power Mode is enabled.CKContainer
is a subclass of theNSObject
class. Interactions withCKContainer
objects occur at a QoS level ofNSQualityOfServiceUserInitiated
by default.CKDatabase
is a subclass of theNSObject
class. Interactions withCKContainer
objects occur at a QoS level ofNSQualityOfServiceUserInitiated
by default.
For information about CloudKit classes, see CloudKit Framework Reference.
Debugging Quality of Service Classes
By setting breakpoints in Xcode or pausing your app while testing, you can inspect your app with the CPU usage gauge in the debug navigator in order to confirm that requested QoS classes are being applied.
Copyright © 2018 Apple Inc. All rights reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13