1. 비동기 작업(Asynchronous Task)이란?

비동기 작업이란, 특정 작업이 완료될 때까지 기다리지 않고, 동시에 다른 작업을 수행할 수 있도록 만드는 프로그래밍 기법이다.
Swift에서는 네트워크 요청, 파일 읽기/쓰기, 애니메이션 등 시간이 걸리는 작업에 비동기 처리를 활용한다.

비동기의 특징


2. 비동기 작업의 실행 흐름 이해하기

에제 코드

let url = URL(string: "https://dev-test-api.com/users")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        print("Error: \(error.localizedDescription)")
        return
    }
    print("Success")
}

task.resume()

print("Done?")

출력 결과

Done?
Success

실행 순서

  1. task.resume() 호출 → 네트워크 요청 시작.
  2. print("Done?") -> 메인 스레드는 요청 대기 없이 다음 코드 실행.
  3. 네트워크 요청 완료 후 -> 클로저 내부 코드 실행(print("Success")).

3. 컴플리션 핸들러(Completion Handler)란?

컴플리션 핸들러는 비동기 작업이 끝난 후 호출되는 클로저(closure)이다.
비동기 작업의 결과를 외부에서 처리하거나 후속 작업을 수행할 수 있게 만든다.

컴플리션 핸들러의 특징


4. 컴플리션 핸들러 추가하기

기존 코드 (컴플리션 핸들러 없음)

func fetchData() {
    let url = URL(string: "https://dev-test-api.com/users")!
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            print("Error: \(error.localizedDescription)")
            return
        }
        print("Success")
    }
    task.resume()
}

컴플리션 핸들러 추가 코드

func fetchUserData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        completion(data, response, error) // 작업 완료 후 결과 전달
    }
    task.resume() // 작업 시작
}

사용 예시

let url = URL(string: "https://dev-test-api.com/users")!

fetchUserData(from: url) { data, response, error in
    if let error = error {
        print("Error: \(error.localizedDescription)")
        return
    }
    if let data = data {
        print("Data received: \(data)")
        // JSON 파싱 처리
        do {
            let json = try JSONSerialization.jsonObject(with: data, options: [])
            print("Parsed JSON: \(json)")
        } catch {
            print("JSON Parsing Error: \(error.localizedDescription)")
        }
    }
}

5. @escaping 키워드란?

컴플리션 핸들러는 함수가 실행된 후 호출될 수 있으므로, @escaping 키워드를 사용해 클로저가 함수의 생명 주기를 벗어날 수 있음을 명시합니다.

@escaping을 사용해야 하는 이유

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        completion("작업 완료") // 함수 종료 후 호출 가능
    }
}

6. 실제 사용 사례

1. 네트워크 요청 처리

func fetchUsers(completion: @escaping ([String]?, Error?) -> Void) {
    let url = URL(string: "https://example.com/api/users")!
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(nil, error) // 에러 전달
            return
        }
        if let data = data {
            do {
                let users = try JSONDecoder().decode([String].self, from: data)
                completion(users, nil) // 성공 시 데이터 전달
            } catch {
                completion(nil, error) // JSON 파싱 에러 전달
            }
        }
    }.resume()
}

// 사용
fetchUsers { users, error in
    if let error = error {
        print("Error: \(error.localizedDescription)")
    } else if let users = users {
        print("Users: \(users)")
    }
}

2. 애니메이션 완료 후 작업

func fadeOut(view: UIView, completion: @escaping () -> Void) {
    UIView.animate(withDuration: 0.5, animations: {
        view.alpha = 0
    }, completion: { _ in
        completion() // 애니메이션 완료 후 호출
    })
}

// 사용
fadeOut(view: someView) {
    print("Fade-out animation completed!")
}

7. 컴플리션 핸들러의 장점

  1. 유연한 후속 작업 처리:
  2. 가독성과 유지보수성 향상:
  3. 비동기 코드 모듈화:

8. 결론

비동기 작업은 현대 앱에서 필수적인 기능이며, 컴플리션 핸들러는 이를 효율적으로 처리하기 위한 강력한 도구입니다.
이를 통해 작업 완료 후의 동작을 유연하게 정의할 수 있으며, 코드를 더 읽기 쉽고 재사용 가능하게 만듭니다.