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 را به طور خودکار انزوان میکند تا فقط در داخل منطقه اکتور در دسترس باشند. در صورت تلاش برای دسترسی به balance از خارج actor، اگر فراخوانی به عنوان async علامتگذاری نشده باشد، کامپایلر خطا خواهد داد.
انزواک دادهها — مفهوم کلیدی actor است. Actor دسترسی متقابل به وضعیت خود را از طریق مکانیسم اجراینده اکتور (actor executor) تضمین میکند. هر actor اجراینده مخصوص خود را دارد که تمامی مراجعات به اعضای انزوان شده آن را به طور متوالی پردازش میکند.
وقتی کد خارج از actor روش آن را فراخوان میکند، فراخوانی در صف انتظار اجراینده اکتور قرار میگیرد. Actor فقط یک وظیفه را همزمان انجام میدهد که عدم وجود مسابقه را تضمین میکند. اگر دو رشته همزمان deposit را فراخوان کنند، فراخوانی دوم منتظر تکمیل اولی میماند.
let account = BankAccount(initialBalance: 1000.0)
// فراخوانی ناهمگبام — از خارج actor مورد نیاز است
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
هر فراخوانی از روش actor به await نیاز دارد، چون actor ممکن است مشغول وظیفه دیگری باشد. این یک اشتباه نیست، بلکه طراحی آگاهانهای برای جلوگیری از مسابقه است. Swift به صورت ضمنی getter و setter ویژگیهای actor را async میکند، بنابراین خواندن ویژگی نیز به await نیاز دارد.
Actor و کلاس هر دو نوع ارجاعی هستند، اما رفتار آنها در محیط چندرشتهای اساساً متفاوت است. کلاس هیچ حفاظت خودکاری در برابر مسابقه تأمین نمیکند، در حالی که actor آن را در سطح کامپایلر از طریق سیستم نوع جاری میکند.
| ویژگی | Actor | Class |
|---|---|---|
| حفاظت در برابر مسابقه | خودکار، در سطح کامپایلر | نیازمند همگامسازی دستی |
| دسترسی به ویژگیها | فقط از طریق await از خارج | مستقیم، بدون همگامسازی |
| ارثبری | فقط از 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() {
// این کد تضمین شده است که روی رشته اصلی اجرا شود
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 — نقطه ورود مجدد
let result = await fetchData(id: id)
// کش ممکن است پس از await تغییر کرده باشد — مجدداً بررسی کنید
cache[id] = result
return result
}
}
توسعهدهندگان باید reentrancy را در نظر بگیرند و وضعیت actor را پس از نقاط await بررسی کنند. یک اشتباه معمولی این است که فرض کنیم انزواک actor در طول توقفهای ناهمگبام حفظ میشود. در عمل، بین await و عبارت بعدی، وضعیت میتواند از حالت مورد انتظار متفاوت باشد.
پرسشهای متداول
Actor به طور خودکار وضعیت خود را از مسابقه دادهها ایزولان میکند، برای دسترسی از خارج به await نیاز دارد. کلاس چنین حفاظتی را فراهم نمیکند — توسعهدهنده خود مسئول همگامسازی از طریق قفلها یا صفوف است. Actor فقط از actor ارثبری میکند، کلاس از کلاس.
بله، actor میتواند از دیگر actor ارثبری کند. زیرکلاس تمام ویژگیها و روشهای انزوان شدهٔ والد را دریافت میکند. Actor نمیتواند از کلاس ارثبری کند، زیرا کلاسها مکانیسم انزواک اکتور را در سطح کامپایلر ندارند.
منطقه actor-isolated — حوزهای از کد است که دسترسی مستقیم به وضعیت تغییرپذیر actor مجاز است. داخل روشهای actor که به عنوان isolated (پیشفرض) علامتگذاری شدهاند، میتوان ویژگیها را بدون await خواند و نوشت. کامپایلر مرزهای انزواک را بررسی میکند.
دادهها از actor از طریق روشهای async که نوعهای Sendable را برمیگردانند، یا از طریق روشهای nonisolated که مقادیر را به عنوان پارامتر میپذیرند، انتقال داده میشوند. همچنین میتوان یک ویژگی async ایجاد کرد که نمایی از وضعیت actor را در قالب یک ساختار Sendable بازگرداند.
بله، actor میتواند با پروتکلها مطابقت داشته باشد. اگر پروتکل شامل الزامات انزوان شده (actor-isolated) باشد، آنها به طور خودکار actor-isolated میشوند. برای روشهای ناهمگبام در پروتکلها، میتوان با استفاده از مارکر isolated مشخص کرد که باید روی actor مشخصی فراخوان شوند.
نتایج
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.