

actor는 Swift 5.5에서 도입된 동시성 관련 타입
데이터 레이스 컨디션을 방지하고 스레드 안전성을 보장하는데 도움을 준다.
actor ChatManager {
private var messages: [String] = []
func addMessage(_ message: String) {
messages.append(message)
}
func getAllMessages() -> [String] {
messages
}
}
actor의 주요 특징:
내부 상태에 대한 동시 접근을 자동으로 직렬화한다.
모든 프로퍼티와 메서드는 기본적으로 격리된다.
await 키워드를 사용하여 비동기적으로 접근해야한다.
@MainActor
class ChatViewModel: ObservableObject {
@Published var messages: [String] = []
private let chatManager = ChatManager()
func sendMessage(_ message: String) async {
await chatManager.addMessage(message)
messages = await chatManager.getAllMessages()
}
}
struct ChatView: View {
@StateObject private var viewModel = ChatViewModel()
@State private var newMessage = ""
var body: some View {
VStack {
List(viewModel.messages, id: \.self) { message in
Text(message)
}
HStack {
TextField("메시지 입력", text: $newMessage)
Button("전송") {
Task {
await viewModel.sendMessage(newMessage)
newMessage = ""
}
}
}
}
}
}
actor 사용 시 주의사항:
actor의 메서드 호출은 항상 비동기적이다.
UI 업데이트는 MainActor에서 수행해야 한다.
데드락을 피하기 위해 actor 간의 순환 참조를 주의해야 한다.
actor는 특히 다음과 같은 상황에서 유용합니다:
공유 리소스에 대한 안전한 접근이 필요할 때
백그라운드 작업과 UI 업데이트를 안전하게 조율해야 할 때
네트워크 요청이나 데이터베이스 작업을 관리할 때
최근 API호출을 동시에 했을때 그에 값이 동시도착하는경우가 있어서
이 문제를 해결하기위해 Actor라는것을 찾았고
그에 대한 정리 문서다.
아래와 같은 코드에서 사용을 했는데, 너무나 애자일한 환경에서 개발을 하다보니,
코드의 완성도나 직관성은 많이 떨어지는 편이다.
private func handleTagResponse(_ data: String) async {
print(data, "태그왔습니다.")
memoState.tag = data
if await memoCreationActor.updateValue(type: "tag", value: data) {
await createMemoIfReady()
}
}
private func handleTitleResponse(_ data: String) async {
print(data, "타이틀왔습니다.")
memoState.title = data
if await memoCreationActor.updateValue(type: "title", value: data) {
await createMemoIfReady()
}
}
private func handleDescriptionResponse(_ data: String) async {
print(data, "des왔습니다.")
memoState.content = data
if await memoCreationActor.updateValue(type: "content", value: data) {
await createMemoIfReady()
}
}
private func createMemoIfReady() async {
await memoCreationActor.startProcessing()
let values = await memoCreationActor.getCurrentValues()
let newMemo = memoData(
category: values.tag,
title: "<" + languageViewModel.strings.searchKeywordTitle + "> " + values.title,
content: values.content,
imageDescription: "",
link: memoState.selectedImagepath,
soundurl: "",
createdAt: ISO8601DateFormatter().string(from: Date()),
memotype: "menu",
Meta_Date: "",
Meta_Location: ""
)
await MainActor.run {
memoManager.addMemo(newMemo)
isLoading = false
}
}
---
import SwiftUI
//
// MemoCreationActor 정의
actor MemoCreationActor {
private var tag: String = ""
private var title: String = ""
private var content: String = ""
private var isProcessing: Bool = false
func updateValue(type: String, value: String) async -> Bool {
switch type {
case "tag":
tag = value
case "title":
title = value
case "content":
content = value
default:
break
}
return shouldCreateMemo()
}
private func shouldCreateMemo() -> Bool {
return !tag.isEmpty && !title.isEmpty && !content.isEmpty &&
tag != "error" && title != "error" && content != "error" &&
!isProcessing
}
func startProcessing() {
isProcessing = true
}
func reset() {
tag = ""
title = ""
content = ""
isProcessing = false
}
func getCurrentValues() -> (tag: String, title: String, content: String) {
return (tag, title, content)
}
}