[Swift] 클로저와 @escaping, Result<> 공부

팔랑이·2024년 12월 12일
0

iOS/Swift

목록 보기
63/71
post-thumbnail

그동안 제대로 정리하지 않고 애매하게 알고 있던 내용들을 글로 정리하기로.
지피티가 그렇게 써주니까 그런가보다 한 것들이 많았는데, 당연한 얘기지만 원리를 이해해야 스스로 쓸 수 있기 때문에...

1. 클로저와 Void 반환

  • 반환값이 있는 클로저
let myClosure: (String) -> Int = { input in
    return input.count
}

보다시피 Int를 반환

  • 반환값이 없는 경우 Void를 사용:
let myClosure: (String) -> Void = { input in
    print(input)
}
  • -> 뒤에 자료형이 명시된 경우, 반드시 반환값을 제공해야 함. 반환하지 않으면 컴파일 에러 발생.
  • 반대로 Void로 반환하기로 선언한 경우, return 키워드만을 사용해 함수를 종료하는 것은 에러가 나지 않음. 단, return 뒤에 뭔가 반환하려고 하면 에러가 난다.

2. @escaping

completion은 싱글턴 패턴의 shared와 같이 관용적으로 쓰는 단어일 뿐이고, 실질적인 기능은 @escaping 키워드가 한다.

  • 기본적으로 클로저는 함수가 종료되면 메모리에서 해제된다.
  • 비동기 작업을 한다고 선언하면, 프로그램은 이 비동기 작업이 끝날 때까지 기다려주지 않고 다음 코드를 실행한다. 만약 비동기 작업이 모두 끝난 후 실행하고 싶은 행동이 있을 때 이 @escaping 키워드를 사용 해 completion의 코드블록을 실행시킬 수 있다.
  • 이렇게 하면 클로저가 메모리에서 해제돼도, 이 클로저의 생명주기에서 벗어나 completion을 실행시킬 수 있다.

예시 코드

func performTask(completion: @escaping () -> Void) {
    DispatchQueue.global().async {
        completion() // 비동기 작업 완료 후 실행된다
    }
}

3. Result<> 타입

func fetchData(completion: @escaping (Result<String, Error>) -> Void) {

이런 코드를 쓸 때, 솔직히 Result<Success, Failure> 이거 뭔지 모르고 썼음...
그래서 switch result .success .failure 왜 이렇게 쓰는지도 몰랐다...

  • 알고 보니 Result<>는 Swift 표준 라이브러리에서 제공하는 열거형 타입이었다.
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

이렇게 정의가 되어있던 거였음.

다음과 같이 쓴다면, 행하는 작업의 결과가 성공이면 String을 반환하고, 실패면 Error를 반환한다는 것.

func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
    DispatchQueue.global().async {
        let success = true
        if success {
            completion(.success("Data fetched successfully")) // 성공 반환
        } else {
            completion(.failure(NSError(domain: "Error", code: -1, userInfo: nil))) // 실패 반환
        }
    }
}

// 호출
fetchData { result in
    switch result {
    case .success(let data):
        print("Success: \(data)")
    case .failure(let error):
        print("Error: \(error.localizedDescription)")
    }
}
  • 저 호출부에서 let data, let error는 Result의 .success, .failure에서 연관된 값을 꺼내오는 변수이다. Result<,>는 제네릭이므로 저 안에 어떤 값이 들어있는지에 따라 달라진다. 연관값이 필요 없다면, _을 통해 무시할 수도 있다.

  • 방금 서술했듯이 Result의 성공 타입은 제네릭이므로, 위의 String 외에 다른 타입도 반환할 수 있다.

func fetchNumber(completion: @escaping (Result<Int, Error>) -> Void) {
    completion(.success(42)) // Int 타입 성공 반환
}

확실히 글로 정리해야 머릿속에서 더 짜맞춰진다.

profile
정체되지 않는 성장

0개의 댓글