Swift 5.5에서 도입된 Actor는 동시성 프로그래밍을 위한 강력한 도구입니다. 하지만 모든 상황에서 Actor가 정답은 아닙니다. 이 글에서는 Actor와 Class의 차이점을 알아보고, 각각을 언제 사용해야 하는지 실용적인 관점에서 살펴보겠습니다.
Actor의 특징:
Class의 특징:
Actor를 사용할 때는 await
키워드가 필수입니다:
// Actor 예제
actor BankAccount {
private var balance = 0
func deposit(amount: Int) {
balance += amount
}
func getBalance() -> Int {
return balance
}
}
// 사용 시
let account = BankAccount()
await account.deposit(amount: 100) // ⚠️ await 필요
let balance = await account.getBalance()
Class는 동기적으로 바로 접근 가능합니다:
// Class 예제
class BankAccount {
private var balance = 0
func deposit(amount: Int) {
balance += amount
}
func getBalance() -> Int {
return balance
}
}
// 사용 시
let account = BankAccount()
account.deposit(amount: 100) // ✅ 직접 호출
let balance = account.getBalance()
await
키워드 필수nonisolated
키워드로 특정 멤버를 격리에서 제외 가능@MainActor
같은 글로벌 액터 지원actor ImageCache {
private var cache: [URL: UIImage] = [:]
func store(_ image: UIImage, for url: URL) {
cache[url] = image
}
func retrieve(for url: URL) -> UIImage? {
return cache[url]
}
}
// 여러 곳에서 동시에 안전하게 접근
Task { await imageCache.store(image1, for: url1) }
Task { await imageCache.store(image2, for: url2) }
actor DatabaseConnection {
private var connection: SQLiteConnection?
func execute(query: String) async throws -> [Row] {
// 여러 스레드에서 동시 접근해도 안전
return try await connection?.execute(query) ?? []
}
}
actor DataProcessor {
private var processedData: [ProcessedItem] = []
func processInBackground(_ rawData: RawData) async {
// 무거운 처리 작업
let processed = await heavyProcessing(rawData)
processedData.append(processed)
}
func getProcessedData() -> [ProcessedItem] {
return processedData
}
}
class UserProfileViewModel {
var name: String = ""
var age: Int = 0
var email: String = ""
func updateProfile(name: String, age: Int, email: String) {
self.name = name
self.age = age
self.email = email
}
}
class CustomViewController: UIViewController {
// UIKit은 Class 기반으로 설계됨
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
}
class ShoppingCart {
private var items: [Product] = []
func addItem(_ product: Product) {
items.append(product)
}
func calculateTotal() -> Double {
return items.reduce(0) { $0 + $1.price }
}
}
// 동시성이 필요 없는데 Actor를 사용
actor SimpleCounter {
var count = 0
func increment() {
count += 1
}
}
// 사용할 때마다 불필요한 await
let counter = SimpleCounter()
await counter.increment() // 왜 기다려야 하지?
// 단순한 카운터는 Class로 충분
class SimpleCounter {
var count = 0
func increment() {
count += 1
}
}
// 깔끔하고 직관적인 사용
let counter = SimpleCounter()
counter.increment() // 바로 실행!
await
, Task
등의 추가 코드가 필요합니다"You Aren't Gonna Need It" - 미래에 필요할 수도 있다는 이유로 복잡한 설계를 미리 하지 마세요.
// ❌ 과도한 설계
// "나중에 멀티스레드가 필요할 수도 있으니까..."
actor UserSettings {
var theme: Theme = .light
var fontSize: Int = 14
}
// ✅ 적절한 설계
// 지금은 Class로 충분. 나중에 필요하면 리팩토링
class UserSettings {
var theme: Theme = .light
var fontSize: Int = 14
}
Swift의 강력한 타입 시스템 덕분에 Class를 Actor로 변경할 때 컴파일러가 수정이 필요한 모든 곳을 알려줍니다. 따라서 처음부터 과도하게 설계할 필요가 없습니다.
Actor는 Swift의 동시성 프로그래밍을 위한 훌륭한 도구이지만, 만능 해결책은 아닙니다. 각 상황에 맞는 적절한 도구를 선택하는 것이 중요합니다.
올바른 도구를 선택하여 더 깔끔하고 유지보수하기 쉬운 코드를 작성하세요! 🚀
이 글이 도움이 되셨다면, 팀원들과 공유해주세요. Swift 동시성에 대한 더 많은 내용은 Apple의 공식 문서를 참고하세요.