UIKIT

Combine 연산자

밤새는 탐험가89 2025. 1. 16. 13:26

Combine에서 map, tryMap, 그리고 flatMap은 데이터 변환과 흐름 제어를 위해 사용되는 연산자로, 각각의 역할과 사용 사례가 다릅니다.

 

1. map

map은 입력 값을 단순 변환하는 연산자입니다.
에러를 발생시키지 않고, 입력 데이터를 동기적으로 처리할 때 사용됩니다.

특징

  • 데이터를 변환하지만, 새로운 Publisher를 생성하지 않습니다.
  • 동기적 변환에 적합합니다.
let publisher = Just(10)
    .map { $0 * 2 } // 10 -> 20
    .sink { print($0) } // 출력: 20
let publisher = Just(10)
    .map { $0 * 2 } // 10 -> 20
    .sink { print($0) } // 출력: 20

 

사용 시점

  • 값의 변환이 필요하지만 에러를 발생시키지 않는 경우.
  • 예: 숫자를 두 배로 만들기, 객체의 특정 속성 추출.

 

 

2. tryMap

tryMap은 map과 유사하지만, 변환 과정에서 에러를 발생시킬 수 있는 경우에 사용됩니다.

특징

  • 변환 중에 에러를 던질 수 있습니다.
  • 에러가 발생하면 Publisher의 흐름이 종료됩니다. 
let publisher = Just("123")
    .tryMap { value -> Int in
        guard let number = Int(value) else {
            throw NSError(domain: "ConversionError", code: -1, userInfo: nil)
        }
        return number
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("완료")
        case .failure(let error):
            print("에러 발생: \(error.localizedDescription)")
        }
    }, receiveValue: { value in
        print(value) // 출력: 123
    })

 

let publisher = Just("/path/to/file.txt")
    .tryMap { path -> String in
        return try String(contentsOfFile: path)
    }

 

 

3. flatMap

flatMap은 입력 데이터를 사용해 새로운 Publisher를 생성하고, 해당 Publisher의 데이터를 합성하는 연산자입니다.
이로써 비동기 작업이나 중첩된 Publisher 흐름을 처리할 수 있습니다.

특징

  • 입력 데이터를 사용해 새로운 Publisher를 반환.
  • 생성된 Publisher의 데이터를 평면화(flatten) 합니다.
  • 비동기 작업 처리에 자주 사용됩니다.
func fetchUserDetails(userID: String) -> AnyPublisher<String, Error> {
    // 네트워크 호출 시뮬레이션
    Future<String, Error> { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            promise(.success("Details for user \(userID)"))
        }
    }
    .eraseToAnyPublisher()
}

let publisher = Just("1234") // User ID
    .flatMap { userID in
        fetchUserDetails(userID: userID) // User ID -> User Details
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("완료")
        case .failure(let error):
            print("에러 발생: \(error.localizedDescription)")
        }
    }, receiveValue: { details in
        print(details) // 출력: Details for user 1234
    })

 

실행 흐름 (타임라인)

  1. Just("1234") 실행:
    • "1234"라는 값을 방출하고 완료됩니다.
    • 이 값이 flatMap으로 전달됩니다.
  2. flatMap 실행:
    • "1234"가 fetchUserDetails(userID:) 함수에 전달되어 비동기 작업 시작.
    • 내부에서 DispatchQueue로 인해 1초 후에 promise(.success("Details for user 1234"))가 호출됩니다.
    • 이 결과는 새로운 Publisher로 방출됩니다.
  3. sink 구독:
    • 비동기 작업이 완료되면 fetchUserDetails에서 방출된 값("Details for user 1234")이 sink의 receiveValue에 전달.
    • 이후 Publisher가 완료되면 sink의 receiveCompletion이 호출됩니다.

 

let numbersPublisher = [1, 2, 3].publisher
let flatMappedPublisher = numbersPublisher.flatMap { number in
    Just(number * 2) // 각 숫자를 두 배로 변환
}
flatMappedPublisher.sink { print($0) } // 출력: 2, 4, 6

 

사용 시점

  • 중첩된 Publisher를 평면화해야 할 때.
  • 비동기 작업을 순차적으로 연결해야 할 때.
  • 예: 네트워크 요청 체이닝, 데이터베이스 쿼리.

 

 

언제 어떤 연산자를 사용해야 할까?

  1. map:
    • 입력 데이터를 변환만 하면 되고, 에러가 발생하지 않는 경우.
    • 예: 숫자 -> 문자열, 객체 속성 추출.
  2. tryMap:
    • 변환 중 에러가 발생할 가능성이 있는 경우.
    • 예: 문자열 -> 숫자 변환, JSON 디코딩.
  3. flatMap:
    • 데이터를 기반으로 새로운 Publisher를 생성하고 이를 평면화해야 하는 경우.
    • 예: 네트워크 요청, 비동기 작업 체이닝.