-
Adote os recursos de concorrência do Swift
Saiba mais sobre os principais conceitos de concorrência no Swift. A concorrência ajuda a melhorar a resposta e o desempenho dos apps, e o Swift facilitar a escrita correta de códigos assíncronos e concorrentes. Abordaremos as etapas que você precisa realizar para transformar um app de thread única em concorrente. Também ajudaremos você a determinar como e quando usar melhor os recursos de concorrência do Swift, seja ao tornar seu código mais assíncrono, movê-lo para o segundo plano ou compartilhar dados em tarefas concorrentes.
Capítulos
- 0:00 - Introdução
- 3:17 - Código de thread única
- 6:00 - Tarefas assíncronas
- 7:24 - Interleaving
- 10:22 - Introdução à concorrência
- 11:07 - Funções concorrentes
- 13:10 - Código não isolado
- 14:13 - Pool de threads concorrentes
- 14:58 - Compartilhar dados
- 15:49 - Tipos de valor
- 17:16 - Tipos isolados de ator
- 18:30 - Classes
- 23:18 - Atores
- 26:12 - Conclusão
Recursos
Vídeos relacionados
WWDC25
WWDC23
-
Buscar neste vídeo...
-
-
3:20 - Single-threaded program
var greeting = "Hello, World!" func readArguments() { } func greet() { print(greeting) } readArguments() greet() -
4:13 - Data types in a the app
struct Image { } final class ImageModel { var imageCache: [URL: Image] = [:] } final class Library { static let shared: Library = Library() } -
4:57 - Load and display a local image
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) throws { let data = try Data(contentsOf: url) let image = decodeImage(data) view.displayImage(image) } func decodeImage(_ data: Data) -> Image { Image() } } final class Library { static let shared: Library = Library() } -
5:36 - Fetch and display an image over the network
import Foundation struct Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) throws { let (data, _) = try URLSession.shared.data(from: url) let image = decodeImage(data) view.displayImage(image) } func decodeImage(_ data: Data) -> Image { Image() } } final class Library { static let shared: Library = Library() } -
6:10 - Fetch and display image over the network asynchronously
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = decodeImage(data) view.displayImage(image) } func decodeImage(_ data: Data) -> Image { Image() } } final class Library { static let shared: Library = Library() } -
7:31 - Creating a task to perform asynchronous work
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() var url: URL = URL("https://swift.org")! func onTapEvent() { Task { do { try await fetchAndDisplayImage(url: url) } catch let error { displayError(error) } } } func displayError(_ error: any Error) { } func fetchAndDisplayImage(url: URL) async throws { } } final class Library { static let shared: Library = Library() } -
9:15 - Ordered operations in a task
import Foundation class Image { func applyImageEffect() async { } } final class ImageModel { func displayImage(_ image: Image) { } func loadImage() async -> Image { Image() } func onButtonTap() { Task { let image = await loadImage() await image.applyImageEffect() displayImage(image) } } } -
9:38 - Fetch and display image over the network asynchronously
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = decodeImage(data) view.displayImage(image) } func decodeImage(_ data: Data) -> Image { Image() } } -
10:40 - Fetch and display image over the network asynchronously
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = decodeImage(data, at: url) view.displayImage(image) } func decodeImage(_ data: Data, at url: URL) -> Image { Image() } } -
11:11 - Fetch over network asynchronously and decode concurrently
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = await decodeImage(data, at: url) view.displayImage(image) } @concurrent func decodeImage(_ data: Data, at url: URL) async -> Image { Image() } } -
11:30 - Implementation of decodeImage
final class View { func displayImage(_ image: Image) { } } final class ImageModel { var cachedImage: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = await decodeImage(data, at: url) view.displayImage(image) } @concurrent func decodeImage(_ data: Data, at url: URL) async -> Image { if let image = cachedImage[url] { return image } // decode image let image = Image() cachedImage[url] = image return image } } -
12:37 - Correct implementation of fetchAndDisplayImage with caching and concurrency
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var cachedImage: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { if let image = cachedImage[url] { view.displayImage(image) return } let (data, _) = try await URLSession.shared.data(from: url) let image = await decodeImage(data) view.displayImage(image) } @concurrent func decodeImage(_ data: Data) async -> Image { // decode image Image() } } -
13:30 - JSONDecoder API should be non isolated
// Foundation import Foundation nonisolated public class JSONDecoder { public func decode<T: Decodable>(_ type: T.Type, from data: Data) -> T { fatalError("not implemented") } } -
15:18 - Fetch over network asynchronously and decode concurrently
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = await decodeImage(data, at: url) view.displayImage(image) } @concurrent func decodeImage(_ data: Data, at url: URL) async -> Image { Image() } } -
16:30 - Example of value types
// Value types are common in Swift import Foundation struct Post { var author: String var title: String var date: Date var categories: [String] } -
16:56 - Sendable value types
import Foundation // Value types are Sendable extension URL: Sendable {} // Collections of Sendable elements extension Array: Sendable where Element: Sendable {} // Structs and enums with Sendable storage struct ImageRequest: Sendable { var url: URL } // Main-actor types are implicitly Sendable @MainActor class ImageModel {} -
17:25 - Fetch over network asynchronously and decode concurrently
import Foundation class Image { } final class View { func displayImage(_ image: Image) { } } final class ImageModel { var imageCache: [URL: Image] = [:] let view = View() func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) let image = await self.decodeImage(data, at: url) view.displayImage(image) } @concurrent func decodeImage(_ data: Data, at url: URL) async -> Image { Image() } } -
18:34 - MyImage class with reference semantics
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scale(by factor: Double) { } } let image = MyImage() let otherImage = image // refers to the same object as 'image' image.scale(by: 0.5) // also changes otherImage! -
19:19 - Concurrently scaling while displaying an image is a data race
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scaleImage(by factor: Double) { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() // Slide content start func scaleAndDisplay(imageName: String) { let image = loadImage(imageName) Task { @concurrent in image.scaleImage(by: 0.5) } view.displayImage(image) } // Slide content end func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } } -
20:38 - Scaling and then displaying an image eliminates the data race
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scaleImage(by factor: Double) { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() func scaleAndDisplay(imageName: String) { Task { @concurrent in let image = loadImage(imageName) image.scaleImage(by: 0.5) await view.displayImage(image) } } nonisolated func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } } -
20:54 - Scaling and then displaying an image within a concurrent asynchronous function
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scaleImage(by factor: Double) { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() @concurrent func scaleAndDisplay(imageName: String) async { let image = loadImage(imageName) image.scaleImage(by: 0.5) await view.displayImage(image) } nonisolated func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } } -
21:11 - Scaling, then displaying and concurrently modifying an image is a data race
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scaleImage(by factor: Double) { } func applyAnotherEffect() { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() // Slide content start @concurrent func scaleAndDisplay(imageName: String) async { let image = loadImage(imageName) image.scaleImage(by: 0.5) await view.displayImage(image) image.applyAnotherEffect() } // Slide content end nonisolated func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } } -
21:20 - Applying image transforms before sending to the main actor
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scaleImage(by factor: Double) { } func applyAnotherEffect() { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() // Slide content start @concurrent func scaleAndDisplay(imageName: String) async { let image = loadImage(imageName) image.scaleImage(by: 0.5) image.applyAnotherEffect() await view.displayImage(image) } // Slide content end nonisolated func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } } -
22:06 - Closures create shared state
import Foundation struct Color { } nonisolated class MyImage { var width: Int var height: Int var pixels: [Color] var url: URL init() { width = 100 height = 100 pixels = [] url = URL("https://swift.org")! } func scale(by factor: Double) { } func applyAnotherEffect() { } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() // Slide content start @concurrent func scaleAndDisplay(imageName: String) async throws { let image = loadImage(imageName) try await perform(afterDelay: 0.1) { image.scale(by: 0.5) } await view.displayImage(image) } nonisolated func perform(afterDelay delay: Double, body: () -> Void) async throws { try await Task.sleep(for: .seconds(delay)) body() } // Slide content end nonisolated func loadImage(_ imageName: String) -> MyImage { // decode image return MyImage() } }pet. -
23:47 - Network manager class
import Foundation nonisolated class MyImage { } struct Connection { func data(from url: URL) async throws -> Data { Data() } } final class NetworkManager { var openConnections: [URL: Connection] = [:] func openConnection(for url: URL) async -> Connection { if let connection = openConnections[url] { return connection } let connection = Connection() openConnections[url] = connection return connection } func closeConnection(_ connection: Connection, for url: URL) async { openConnections.removeValue(forKey: url) } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() let networkManager: NetworkManager = NetworkManager() func fetchAndDisplayImage(url: URL) async throws { if let image = cachedImage[url] { view.displayImage(image) return } let connection = await networkManager.openConnection(for: url) let data = try await connection.data(from: url) await networkManager.closeConnection(connection, for: url) let image = await decodeImage(data) view.displayImage(image) } @concurrent func decodeImage(_ data: Data) async -> MyImage { // decode image return MyImage() } } -
25:10 - Network manager as an actor
import Foundation nonisolated class MyImage { } struct Connection { func data(from url: URL) async throws -> Data { Data() } } actor NetworkManager { var openConnections: [URL: Connection] = [:] func openConnection(for url: URL) async -> Connection { if let connection = openConnections[url] { return connection } let connection = Connection() openConnections[url] = connection return connection } func closeConnection(_ connection: Connection, for url: URL) async { openConnections.removeValue(forKey: url) } } final class View { func displayImage(_ image: MyImage) { } } final class ImageModel { var cachedImage: [URL: MyImage] = [:] let view = View() let networkManager: NetworkManager = NetworkManager() func fetchAndDisplayImage(url: URL) async throws { if let image = cachedImage[url] { view.displayImage(image) return } let connection = await networkManager.openConnection(for: url) let data = try await connection.data(from: url) await networkManager.closeConnection(connection, for: url) let image = await decodeImage(data) view.displayImage(image) } @concurrent func decodeImage(_ data: Data) async -> MyImage { // decode image return MyImage() } }
-
-
- 0:00 - Introdução
A concorrência do Swift permite que os apps executem várias tarefas de maneira simultânea, melhorando a capacidade de resposta e enviando a computação para o segundo plano. O modelo de concorrência do Swift facilita a escrita correta de código concorrente ao tornar explícita a introdução da concorrência, identificando os dados que são compartilhados entre tarefas concorrentes e identificando possíveis corridas de dados em tempo de compilação. Os apps começam executando todo o código na thread principal. Conforme a complexidade aumenta, tarefas assíncronas podem ser introduzidas para operações de alta latência, como acesso à rede. As threads em segundo plano podem ser utilizadas para tarefas que consomem mais computação. O Swift oferece várias ferramentas, como atores e tarefas, para expressar essas operações concorrentes.
- 3:17 - Código de thread única
No Swift, código de thread única é executado na thread principal, que fica isolada para o ator principal. Não há concorrência no ator principal porque há apenas uma thread principal que pode executá-lo. Dados ou códigos podem ser especificados no ator principal usando a notação @MainActor. O Swift garantirá que o código do ator principal seja executado apenas na thread principal e que os dados sejam acessados somente a partir dela. Dizemos que tal código é isolado do ator principal. Por padrão, o Swift protege o código da thread principal usando o ator principal, garantindo acesso livre ao estado compartilhado. A proteção do código com o ator principal por padrão é orientada por um ajuste de compilação. Use isso principalmente para seu módulo de app principal e módulos focados em interações da UI.
- 6:00 - Tarefas assíncronas
A concorrência do Swift utiliza async e await para fazer com que as funções não sejam bloqueadoras, permitindo que outros trabalhos, como solicitações de rede, sejam executados enquanto os dados são aguardados. Isso impede travamentos e melhora a capacidade de resposta da UI, dividindo as funções em partes que são executadas antes e depois do evento esperado.
- 7:24 - Interleaving
As funções assíncronas são executadas em uma tarefa, independentemente de outras tarefas. Uma única thread pode alternar entre a execução de tarefas independentes à medida que elas ficam prontas, usando “interleaving”. Isso melhora o desempenho, evitando o tempo ocioso e fazendo uso eficiente dos recursos do sistema. Várias tarefas assíncronas são eficazes ao executar várias operações independentes ao mesmo tempo. Ao executar os trabalhos em uma ordem específica, utilize uma única tarefa. Usar tarefas assíncronas em uma única thread costuma ser suficiente. Se a thread principal ficar muito sobrecarregada, as ferramentas de profiling, como o Instruments, podem ajudar a identificar gargalos para otimização antes de implementar a concorrência.
- 10:22 - Introdução à concorrência
A concorrência possibilita que partes do código sejam executadas em uma thread em segundo plano paralelamente à thread principal, acelerando o trabalho ao usar mais núcleos de CPU no seu sistema. Para melhorar o desempenho, o exemplo implementa a concorrência para executar código em threads em segundo plano, liberando a thread principal.
- 11:07 - Funções concorrentes
Ao aplicar o atributo @concurrent, o Swift é instruído a executar uma função em segundo plano. O compilador do Swift destaca o acesso aos dados no ator principal para implementar a concorrência de maneira segura. Uma prática recomendada para garantir que o trabalho ocorra de maneira síncrona é mover o código do ator principal para um chamador que sempre seja executada na thread principal.
- 13:10 - Código não isolado
Uma função @concurrent sempre se desligará de um ator para ser executada. A palavra-chave “nonisolated” possibilita que os clientes escolham onde executar o código: na thread principal ou em segundo plano. Para bibliotecas de uso geral, recomenda-se fornecer uma API não isolada e deixar que os clientes decidam se desejam descarregar o trabalho. Para mais opções de concorrência, consulte “Beyond the basics of structured concurrency” da WWDC23.
- 14:13 - Pool de threads concorrentes
Ao colocar o trabalho em segundo plano, o sistema gerencia o agendamento do trabalho em threads em um pool de threads concorrentes. Dispositivos menores terão menos threads no pool, enquanto sistemas maiores com mais núcleos terão mais. As tarefas são atribuídas às threads disponíveis no pool, que podem mudar à medida que as tarefas são suspensas e retomadas, otimizando a utilização dos recursos.
- 14:58 - Compartilhar dados
Ao trabalhar com concorrência e compartilhar dados entre diferentes threads, existe o risco de introduzir bugs de tempo de execução devido ao acesso ao estado mutável compartilhado. O design do Swift ajuda a mitigar esse problema ao fornecer verificações em tempo de compilação, possibilitando que os desenvolvedores escrevam códigos concorrentes com mais confiança.
- 15:49 - Tipos de valor
O uso de tipos de valores oferece uma vantagem significativa ao lidar com tarefas concorrentes. Quando um tipo de valor é copiado para uma thread em segundo plano, é criada uma cópia independente, garantindo que quaisquer alterações feitas na thread principal não afetem o valor da thread em segundo plano. Essa independência torna os tipos de valores seguros para compartilhamento entre threads. Os tipos de valores que estão em conformidade com o protocolo “Sendable” são sempre seguros para serem compartilhados de maneira concorrente. Os tipos de ator principal são implicitamente “Sendable”.
- 17:16 - Tipos isolados de ator
Os atores do Swift protegem o estado “não Sendable”, garantindo o acesso a uma única tarefa. Quando os valores são enviados de e para os atores, o compilador do Swift verifica a segurança.
- 18:30 - Classes
No Swift, as classes são tipos de referência, o que significa que as alterações em um objeto por meio de uma variável são visíveis em todas as variáveis que apontam para esse objeto. Quando várias threads acessam e modificam o mesmo objeto “não Sendable” de maneira concorrente, isso pode resultar em corridas de dados, travamentos ou falhas. O sistema de concorrência do Swift evita isso em tempo de compilação, reforçando que somente os tipos “Sendable” são compartilhados entre atores e tarefas. Para evitar corridas de dados, é fundamental garantir que os objetos mutáveis não sejam compartilhados de forma concorrente. Conclua as modificações nos objetos antes de enviá-los a outra tarefa ou ator para exibição ou processamento. Quando um objeto precisar ser modificado em uma thread em segundo plano, torne-o “nonisolated”, mas não “Sendable”. Os fechamentos com estado compartilhado também podem ser seguros, desde que não sejam chamados de forma concorrente.
- 23:18 - Atores
À medida que um app cresce, o ator principal pode gerenciar muitos estados, o que leva à troca frequente de contexto. A implementação de atores pode reduzir isso. Os atores isolam seus dados, permitindo que uma única thread os acesse por vez, evitando a contenção. Ao mover o estado do ator principal para atores dedicados, é possível executar mais código de forma concorrente em threads em segundo plano. Isso libera a thread principal a fim de manter a capacidade de resposta. As classes voltadas para a UI e as classes de modelos geralmente precisam permanecer no ator principal ou ser mantidas como “não Sendable”.
- 26:12 - Conclusão
Os apps geralmente começam com uma única thread e evoluem para usar tarefas assíncronas, código concorrente e atores para melhorar o desempenho. A concorrência do Swift ajuda nessa transição, facilitando a transferência do código para fora da thread principal e melhorando a capacidade de resposta. As ferramentas de profiling, como o Instruments, ajudam a identificar quando e qual código precisa ser transferido para fora da thread principal. Use as configurações de compilação recomendadas para ajudar a simplificar a implementação da concorrência e a configuração Approachable Concurrency para ativar um pacote de recursos futuros que vão facilitar o trabalho com a concorrência.