# escaping 클로저

escaping 키워드는 클로저의 파라미터가 힙에 저장되어 함수 스택 프레임 해제 이후에도 사용될 때 붙인다.

class Data {
    var x = 10

    func doSomething() {
        escapingClosure { [self] in
            x = 100
        }
        nonEscapingClosure {
            x = 100
        }
    }
}

var completionHandler: (() -> Void)?

/// escapingClosure
func escapingClosure(closure: @escaping () -> Void) {
    completionHandler = closure
}

/// nonEscapingClosure
func nonEscapingClosure(closure: () -> Void) {
    closure()
}

escapingClosure가 실행될때 클로저 함수 호출이 종료되고 스택 프레임이 해제된 이후에도 Data 클래스 인스턴스가 힙에서 활용될 것이기 때문에 명시적인 캡처 리스트로 self를 추가해야 한다.

# (참고) @retroactive

extension String: @retroactive Error {}

String타입을 확장하여 Error 프로토콜을 채택하는데, Foundation 프레임워크에 실제로 Error프로토콜을 채택한채로 릴리즈되면 프로토콜 중복 채택으로 에러가 발생한다.

이러한 에러를 고려하여 @retroactive키워드를 추가하게 된다.

# Task

Task는 비동기적인 일처리를 할 수 있는 하나의 작업 단위이다. 각 태스크는 병렬적으로 실행되고 태스크 내부에서는 호출 순서를 보장하여 실행된다.

태스크는 두 개의 파라미터로 만들 수 있다.

Task(priority: TaskPriority?, operation: () async throws -> Sendable)

태스크는 task.cancel() 메서드를 호출하여 중간에 취소 할수도 있다.

특정 타입의 값을 리턴할 수도 있다.

let task: Task<String, Never> = Task {
    print("비동기 작업")
    return "작업 끝"
}

/// 작업 취소
task.cancel()

Task {
    await task.value // 리턴값
    await task.result // 태스크 성공여부
}

# Task 컨텍스트

태스크는 현재 실행중인 컨텍스트의 메타데이터를 그대로 상속하여 사용한다. (우선 순위, 실행 액터, 지역변수)

작업 취소 여부는 상속되지 않는다.

Task(priority: .background) {
    sleep(2)
    print("비동기 실행")

    // background 우선순위 자동 상속
    Task {
        print("중첩 Task 생성")
    }
}

Task 내부에서 호출되는 비동기 함수를 중간에 중단했다가 다시 복귀하도록 만들 수 있다. 이것이 Task의 주요한 특징이다.

# Task 클로저 내에서 self의 사용

class Worker {
    var work: Task<Void, Never>?
    var result: Worker?

    func start() {
        work = Task {
            result = Work()
        }
    }
}

Task는 현재 컨텍스트를 암시적으로 캡처한다. 위 코드에서 result라는 변수는 반드시 self를 캡처하는 동작밖에 하지 않는 것이다. 따라서 명시적인 self캡처를 하지 않아도 된다.

또한 weak self과 같은 약한 참조도 불필요하다. Task 내부에서는 작업이 끝나는 즉시 self에 대한 참조가 자동으로 해제된다.

기존 DispatchQueue 기반에서 이스케이핑 클로저가 외부 변수를 참조하는 경우 반드시 명시적인 캡처가 필요하다.