티스토리 뷰

🍏/Swift

[Swift] @TaskLocal

eungding 2024. 8. 23. 23:51
728x90
반응형

[1] @TaskLocal 이란 ?

 

@TaskLocal  은 특정 Task 와 Child Tasks 들끼리 값을 공유할 수 있게 해주는 프로퍼티 래퍼입니다. 

 

이 값은 특정 Task 랑 Child Tasks 에만 국한되어서 전파됩니다. 

즉 상위 Task 및 다른 독립적인 Task 에는 영향을 주지 않습니다.

 

@TaskLocal  를 통해 전역상태를 피하면서도,  관련된 작업들 간에 상태를 안전하게 공유할 수 있습니다. 

 

 

https://www.kodeco.com/books/modern-concurrency-in-swift

 

 

 

[2] TaskLocal 선언하기 

static 이나 global 프로퍼티로 선언되어야합니다. 

(global 은 Swift 6 이상부터 가능하다고 하네요) 

 

 

 

[3]  TaskLocal 에 값을 바인딩 하기 

task-local 에 직접적으로 value 를 set 할 수 없습니다.  

대신에 withValue { .. }  operation 을 통해 값을 바인딩할 수 있습니다.

 

 

 

그리고 이 값은 closure 내에서만 한정됩니다. 

 

 

 

위에서 말한 것 처럼 상위 Task 랑 독립적인 Task 에는 영향을 안주는 것도 확인할 수 있습니다.

 

 

 

 

[4]  TaskLocal  유용한 예제 

- 로그 분석 및 디버깅

- 데이터가 Task Group 마다 독립적으로 필요한데,  scope 분리가 보장되어야할 때  (특히 Task 트리가 복잡할 때) 

 

유용할 것 같습니다. 

 

 

~ GPT 가 작성해준 예제 첨부합니다 ~  (막 그렇게 fit 하지 않는데 어떤 느낌인 지 알 수 있는 정도 .. ? 느낌만 봐주세요)

 

1) 네트워크 요청추적 

import Foundation

// 1. TaskLocal 정의: 각 요청의 RequestID를 저장
enum RequestContext {
    @TaskLocal static var requestID: String?
}

// 2. 네트워크 요청 처리 함수
func performNetworkRequest(url: String) async {
    // 각 요청마다 고유한 RequestID 생성
    let currentRequestID = UUID().uuidString

    // TaskLocal의 requestID 값을 설정
    await RequestContext.$requestID.withValue(currentRequestID) {
        // 실제 네트워크 요청을 수행 (가정)
        logMessage("Sending request to \(url)")
        await sendRequest(to: url)
        logMessage("Received response from \(url)")
    }
}

// 3. 로그 메시지 출력 함수
func logMessage(_ message: String) {
    // 현재 TaskLocal의 requestID 값을 사용하여 로그 출력
    if let requestID = RequestContext.requestID {
        print("[RequestID: \(requestID)] \(message)")
    } else {
        print(message)
    }
}

// 4. 네트워크 요청을 시뮬레이션하는 함수
func sendRequest(to url: String) async {
    // 네트워크 요청이 걸리는 시간을 시뮬레이션
    try? await Task.sleep(nanoseconds: 1_000_000_000) // 1초 대기
    logMessage("Request completed for \(url)")
}

// 5. 여러 네트워크 요청을 동시에 수행
Task {
    await performNetworkRequest(url: "https://example.com/api/1")
}

Task {
    await performNetworkRequest(url: "https://example.com/api/2")
}


// 출력되는 로그 
[RequestID: 123e4567-e89b-12d3-a456-426614174000] Sending request to https://example.com/api/1
[RequestID: 123e4567-e89b-12d3-a456-426614174001] Sending request to https://example.com/api/2
[RequestID: 123e4567-e89b-12d3-a456-426614174000] Request completed for https://example.com/api/1
[RequestID: 123e4567-e89b-12d3-a456-426614174000] Received response from https://example.com/api/1
[RequestID: 123e4567-e89b-12d3-a456-426614174001] Request completed for https://example.com/api/2
[RequestID: 123e4567-e89b-12d3-a456-426614174001] Received response from https://example.com/api/2

 

 

2) 사용자 인증 정보 전달 

import Foundation

// TaskLocal을 정의하여 인증 정보를 저장할 수 있도록 설정
@TaskLocal static var currentUser: String?

func performAuthenticatedTask() async {
    // currentUser 값을 설정하고 Task를 생성
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            await TaskLocal.currentUser.withValue("User1") {
                await doSomeWork()
            }
        }

        group.addTask {
            await TaskLocal.currentUser.withValue("User2") {
                await doSomeWork()
            }
        }
    }
}

func doSomeWork() async {
    // 현재 Task의 currentUser 값에 접근
    if let user = TaskLocal.currentUser {
        print("Executing task for user: \(user)")
        // 사용자별 작업 수행
    } else {
        print("No user information available.")
    }
}

// 실행 예제
Task {
    await performAuthenticatedTask()
}

 

 

 

반응형
댓글