Actor

Little_Machine_Human_·2025년 1월 7일
post-thumbnail

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)
    }
}
profile
while(true){ 가족(); 건강(); 자기개발(); }

0개의 댓글