Actor — је тип представљен у Swift 5.5+ који решава проблем трке података на нивоу језика. За разлику од ручних блокада и редова DispatchQueue, actor аутоматски изолише своје стање и синхронизује приступ њему. То значи да две нити не могу истовремено изменити исто својство типа actor, што елиминише стање трке без додатног напора развојача. Механизам се заснива на концепту изолације актора, када компилер прати приступ својствима и методама actor-а. Према Apple, 2025, модел актора је фундаментални део система конкурентности Swift-а.
Главно
Actor — је референцни тип, сличан класи, али са аутоматском заштитом од трке података. Представљен је у Swift 5.5 као део система конкурентности заједно са async/await и Task. Actor гарантује да његово промењиво стање никада неће бити истовремено прочитано или записано из различитих нити без експлицитне синхронизације.
За декларацију actor-а користи се кључна реч actor, након чега следе витичасте заграде са његовим члановима. Синтаксички, actor подсећа на класу, али понашање је радикално другачије.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
Компилер аутоматски изолише сва својства и методе actor-а тако да постају доступни само унутар контекста actor-а. Покушај приступа balance-у изван actor-а ће произвести грешку компилера ако позив није означен као async.
Изолација података — је кључни концепт actor-а. Actor гарантује међусобно искључив приступ свом стању кроз механизам извршиоца actor-а (actor executor). Сваки actor има сопственог извршиоца који обрађује све приступе његовим изолованим члановима секвенцијално.
Када код изван actor-а позива његову методу, позив се ставља у ред извршиоца actor-а. Actor извршава само један задатак истовремено, што гарантује одсуство трке. Ако две нити истовремено позову deposit, други позив чека завршетак првог.
let account = BankAccount(initialBalance: 1000.0)
// Asinhroni poziv — zahtevan izvan actor-a
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Сваки позив методе actor-а захтева await, јер би actor могао бити заузет другим задатком. То није грешка, већ свесни дизајн који спречава трку. Swift имплицитно чини гетере и сетере својстава actor-а async, па читање својства такође захтева await.
Actor и класа су референцни типови, али њихово понашање у вишенитном окружењу се радикално разликује. Класа не пружа никакву аутоматску заштиту од трке, док actor је уграђује на нивоу компилера кроз систем типова.
| Карактеристика | Actor | Class |
|---|---|---|
| Заштита од трке | Аутоматска, на нивоу компилера | Захтева ручну синхронизацију |
| Приступ својствима | Само кроз await споља | Директан, без синхронизације |
| Насљеђивање | Само од других actor-а | Стандардно класно |
| Придржавање протоколима | Може да се придржава протоколима | Стандардно |
| Перформансе | Ниска прекошивност при изолацији | Без синхронизације — брже |
Actor може да насљеди само од другог actor-а и не може да насљеди од класе. То је урађено намерно, јер класа нема механизам изолације actor-а, и мешавина два модела би довела до недефинисаног понашања.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Асинхрони позиви — су механизам интеракције са actor-ом из спољашњег кода. Пошто actor изолише своје стање, сваки приступ његовим члановима споља захтева await. То омогућава Swift-у да гарантује да позивни код не блокира нит и да actor може да обрађује друге захтеве.
Поред декларисаних типова actor-а, Swift подржава глобалне actor-е — атрибут @MainActor, који означава класе, својства или методе као оне које се извршавају на главној нити. То је посебно корисно при раду са UIKit и SwiftUI.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Ovaj kod je garantovano izvršen na glavnoj niti
title = "New Title"
}
}
Коришћењем @MainActor елиминише се потреба за ручним позивом DispatchQueue.main.async, што чини код чишћим и сигурнијим. Компилер проверава да се пребацивање на главну нит одвија исправно.
Nonisolated — је кључна реч која омогућава означавање методе или израчунатог својства actor-а као неизолованог. Такви чланови немају приступ изолованом стању actor-а, али се могу позивати без await споља actor-а.
Nonisolated методе су корисне за израчунавања која не зависе од промењивог стања actor-а. На примјер, метода formatBalance не приступа директно balance-у, већ само форматира прослеђеђену вриједност — такву методу је сигурно могуће учинити nonisolated.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Nonisolated чланови се извршавају синхроно и не захтевају await. Међутим, они не могу директно да читају изоловане својства actor-а. Ако nonisolated методи треба вриједност из actor-а, она се мора проследити као параметар.
Reentrancy — је механизам који омогућава поновни улазак у actor током чекања асинхроног позива. Без reentrancy-ја, actor би могао заувека да се блокира ако једна његова метода чека другу која заузврат чека прву.
Када код унутар actor-а извршава await, actor привремено зауставља тренутни задатак и може да обради други постављен у ред. Након завршетка await-а, задатак се наставља. То спречава deadlock-е, али захтева опрезност: стање actor-а између тачака await се може променити.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — tačka ponovnog ulaska
let result = await fetchData(id: id)
// Keš se može promeniti nakon await — proveri ponovo
cache[id] = result
return result
}
}
Развојачи морају да узму у обзир reentrancy и провере стање actor-а након тачака await. Типична грешка је претпоставка да се изолација actor-а одржава кроз асинхроне паузе. У пракси, између await-а и следеће инструкције, стање се може разликовати од очекиваног.
Често постављана питања
Actor аутоматски изолише своје стање од трке података, захтевајући await за приступ споља. Класа не пружа такву заштиту — развојач сам одговара за синхронизацију кроз блокаде или редове. Actor насљеђује само од actor-а, класа од класе.
Да, actor може да насљеди од другог actor-а. Подкласа добија сва изолована својства и методе родитеља. Actor не може да насљеди од класе, јер класе немају механизам изолације actor-а на нивоу компилера.
Actor-isolated контекст — је област кода у којој је дозвољен директан приступ промењивом стању actor-а. Унутар метода actor-а означених као isolated (по подразумевању), може се читати и писати својства без await. Компилер проверава границе изолације.
Подаци из actor-а се преносе кроз async-методе које враћају Sendable типове, или кроз nonisolated методе које примају вриједности као параметре. Такође се може креирати async својство које враћа снимак стања actor-а у облику Sendable структуре.
Да, actor може да придржава протоколима. Ако протокол садржи изоловане захтеве (actor-isolated), они аутоматски постају actor-изоловани. За асинхроне методе у протоколима, може се навести да се позивају на одређеном actor-у помоћу маркера isolated.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође