Combine

PassthroughSubject에 대해서 설명하시오

PassthroughSubject는 Combine 프레임워크에서 제공하는 클래스로, 외부에서 값을 직접 발행할 수 있도록 하는 Publisher입니다. 이 Subject는 아무 값이나 발행하지 않고, 외부로부터 명시적으로 발행된 값을 전달합니다. 주로 프로그래머가 직접 이벤트를 발행해야 하는 경우에 사용됩니다.

예를 들어, 버튼 클릭 이벤트를 스트림으로 처리하거나, 사용자가 직접 발행해야 하는 커스텀 이벤트에 사용될 수 있습니다.

import Combine

let subject = PassthroughSubject<String, Never>()

subject.sink { value in
    print("Received value: \(value)")
}

subject.send("Hello, World!")

@Published에 대해서 설명하시오

@Published는 SwiftUI와 Combine 프레임워크에서 사용되는 속성 래퍼(Property Wrapper)로, 값이 변경될 때마다 자동으로 이벤트를 발행하는 Publisher를 생성합니다. 주로 SwiftUI의 상태 관리를 위해 사용되며, 뷰가 모델의 상태 변화를 감지하고 이에 따라 업데이트될 수 있도록 합니다.

import Combine
import SwiftUI

class ViewModel: ObservableObject {
    @Published var text: String = "Hello"
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        Text(viewModel.text)
    }
}

AnyCancellable에 대해서 설명하시오

AnyCancellable은 Combine 프레임워크에서 제공하는 타입으로, Publisher의 구독을 관리하고, 구독을 취소할 수 있는 핸들을 제공합니다. 주로 구독을 수동으로 취소하거나, 구독이 필요 없을 때 자동으로 취소되도록 할 때 사용됩니다. AnyCancellable은 구독을 취소하기 위해 호출할 cancel() 메서드를 가지고 있습니다.

import Combine

var cancellable: AnyCancellable?

let subject = PassthroughSubject<String, Never>()

cancellable = subject.sink { value in
    print("Received value: \(value)")
}

subject.send("Hello")

// 구독 취소
cancellable?.cancel()

sink에 대해서 설명하시오

sink는 Combine 프레임워크에서 Publisher의 이벤트를 구독하고, 이벤트를 처리하는 클로저를 제공하는 메서드입니다. sink는 Publisher가 발행하는 값을 받아 처리하는 방법을 정의할 수 있도록 합니다. 이를 통해 Publisher의 값이 변경될 때마다 특정 동작을 수행할 수 있습니다.

import Combine

let subject = PassthroughSubject<String, Never>()

let cancellable = subject.sink { value in
    print("Received value: \(value)")
}

subject.send("Hello, Combine!")

throttle과 debounce의 차이점을 설명하시오

throttledebounce는 이벤트 스트림을 제어하는 데 사용되는 연산자로, 이벤트 발생 빈도를 조절하는 데 사용됩니다.

  • throttle: 주어진 시간 간격 동안 발생한 첫 번째 이벤트만 발행합니다. 이 연산자는 주기적으로 이벤트를 방출하여 이벤트 발생 빈도를 제어합니다. 지정된 간격 동안 다른 이벤트가 무시됩니다.

import Combine

let subject = PassthroughSubject<String, Never>()
let throttled = subject.throttle(for: .seconds(1), scheduler: RunLoop.main, latest: true)

let cancellable = throttled.sink { value in
    print("Received value: \(value)")
}
  • debounce: 주어진 시간 간격 동안 발생한 마지막 이벤트를 발행합니다. 이 연산자는 지정된 간격 동안 이벤트가 더 이상 발생하지 않을 때까지 대기한 후 마지막 이벤트를 방출합니다.

import Combine

let subject = PassthroughSubject<String, Never>()
let debounced = subject.debounce(for: .seconds(1), scheduler: RunLoop.main)

let cancellable = debounced.sink { value in
    print("Received value: \(value)")
}

Data를 Binding 하는 방법에 대해서 설명하시오

SwiftUI에서 Binding은 두 개의 뷰 간에 데이터의 상태를 동기화하는 데 사용됩니다. @State, @ObservedObject, @EnvironmentObject 등을 사용하여 상태를 관리할 때, Binding을 사용하면 부모 뷰의 상태를 자식 뷰에 전달하여 양방향 데이터 바인딩을 구현할 수 있습니다.

import SwiftUI

struct ContentView: View {
    @State private var text: String = "Hello"
    
    var body: some View {
        VStack {
            TextField("Enter text", text: $text)
            Text("You entered: \(text)")
        }
    }
}

여기서 TextField$text 바인딩을 사용하여 text 상태 변수를 업데이트합니다.

Combine에서 Just와 Future의 차이점은 무엇인가요?

  • Just: Just는 단일 값을 즉시 발행하는 Publisher입니다. 성공적으로 한 번의 값만 발행하고 완료(completion)됩니다. 주로 테스트나 간단한 값 발행에 사용됩니다.

    import Combine
    
    let justPublisher = Just("Hello, Combine!")
    justPublisher.sink(receiveCompletion: { completion in
        print("Completed: \(completion)")
    }, receiveValue: { value in
        print("Received value: \(value)")
    })
  • Future: Future는 비동기 작업의 결과를 나중에 발행하는 Publisher입니다. 비동기 작업의 성공 또는 실패 결과를 한 번 발행합니다. 주로 네트워크 요청이나 다른 비동기 작업에 사용됩니다.

    import Combine
    
    let futurePublisher = Future<String, Error> { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            promise(.success("Hello, Future!"))
        }
    }
    futurePublisher.sink(receiveCompletion: { completion in
        print("Completed: \(completion)")
    }, receiveValue: { value in
        print("Received value: \(value)")
    })

Combine의 flatMap 연산자는 언제 사용하나요?

flatMap 연산자는 여러 Publisher를 하나의 스트림으로 합치는 데 사용됩니다. 각 입력 값에 대해 새로운 Publisher를 생성하고, 이 Publisher들이 발행하는 모든 값들을 단일 스트림으로 병합합니다. 주로 비동기 작업이 중첩되는 상황에서 사용됩니다.

import Combine

let numbers = [1, 2, 3].publisher
let flatMapped = numbers.flatMap { number in
    return Just(number * 2)
}

flatMapped.sink(receiveCompletion: { completion in
    print("Completed: \(completion)")
}, receiveValue: { value in
    print("Received value: \(value)")
})

Combine에서 merge와 combineLatest의 차이점은 무엇인가요?

  • merge: merge 연산자는 여러 Publisher를 병합하여 하나의 스트림으로 만듭니다. 각 Publisher가 발행하는 이벤트를 시간 순서대로 방출합니다.

    import Combine
    
    let publisher1 = PassthroughSubject<String, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    
    let merged = publisher1.merge(with: publisher2)
    
    merged.sink { value in
        print("Received value: \(value)")
    }
    
    publisher1.send("Hello")
    publisher2.send("World")
  • combineLatest: combineLatest 연산자는 여러 Publisher의 최신 값을 결합하여 하나의 스트림으로 만듭니다. 모든 Publisher가 적어도 한 번 값을 발행해야 결합된 값을 방출합니다.

    import Combine
    
    let publisher1 = PassthroughSubject<String, Never>()
    let publisher2 = PassthroughSubject<String, Never>()
    
    let combined = publisher1.combineLatest(publisher2)
    
    combined.sink { value1, value2 in
        print("Received values: \(value1), \(value2)")
    }
    
    publisher1.send("Hello")
    publisher2.send("World")

@ObservedObject와 @StateObject의 차이점은 무엇인가요?

  • @ObservedObject: @ObservedObject는 다른 곳에서 소유하는 객체를 관찰하기 위해 사용됩니다. 이 객체의 상태가 변경되면 해당 뷰가 업데이트됩니다. 객체의 생명주기를 관리하지 않기 때문에 부모 뷰가 객체를 소유합니다.

    class ViewModel: ObservableObject {
        @Published var text: String = "Hello"
    }
    
    struct ContentView: View {
        @ObservedObject var viewModel = ViewModel()
        
        var body: some View {
            Text(viewModel.text)
        }
    }
  • @StateObject: @StateObject는 뷰 내부에서 객체를 소유하고 관리하기 위해 사용됩니다. 이 객체의 생명주기를 뷰가 책임지며, 뷰가 초기화될 때 한 번만 초기화됩니다.

    class ViewModel: ObservableObject {
        @Published var text: String = "Hello"
    }
    
    struct ContentView: View {
        @StateObject var viewModel = ViewModel()
        
        var body: some View {
            Text(viewModel.text)
        }
    }

Combine의 catch 연산자는 어떻게 사용되나요?

catch 연산자는 에러가 발생했을 때 대체 Publisher를 제공하여 스트림을 복구하는 데 사용됩니다. 에러가 발생하면 대체 Publisher가 값을 발행하여 스트림이 끊기지 않도록 합니다.

import Combine

let subject = PassthroughSubject<Int, Error>()

let caught = subject.catch { error in
    return Just(0)
}

caught.sink(receiveCompletion: { completion in
    print("Completed: \(completion)")
}, receiveValue: { value in
    print("Received value: \(value)")
})

subject.send(completion: .failure(NSError(domain: "", code: -1, userInfo: nil)))

SwiftUI에서 @EnvironmentObject는 무엇이고, 언제 사용하나요?

@EnvironmentObject는 뷰 계층 구조 전체에 걸쳐 공유되는 객체를 관리하기 위해 사용됩니다. 주로 앱의 전역 상태를 관리하거나 여러 뷰 간에 공유해야 하는 데이터를 전달할 때 사용됩니다.

class AppState: ObservableObject {
    @Published var count: Int = 0
}

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    
    var body: some View {
        VStack {
            Text("Count: \(appState.count)")
            Button("Increment") {
                appState.count += 1
            }
        }
    }
}

Combine에서 Publisher와 Subscriber의 관계를 설명하세요.

  • Publisher: Publisher는 데이터를 생성하고 발행하는 객체입니다. Publisher는 여러 이벤트(값 또는 완료/실패 이벤트)를 발행할 수 있습니다. Publishersubscribe 메서드를 통해 Subscriber와 연결됩니다.

  • Subscriber: SubscriberPublisher가 발행하는 데이터를 수신하는 객체입니다. Subscriberreceive(subscription:), receive(_:), receive(completion:) 메서드를 통해 데이터를 처리합니다.

두 객체는 subscribe 메서드를 통해 연결되며, Publisher가 데이터를 발행하면 Subscriber가 이를 수신하여 처리합니다.

import Combine

let publisher = Just("Hello, Combine!")
let subscriber = Subscribers.Sink<String, Never> { completion in
    print("Completed: \(completion)")
} receiveValue: { value in
    print("Received value: \(value)")
}

publisher.subscribe(subscriber)

Combine의 switchToLatest 연산자는 어떻게 작동하나요?

switchToLatest 연산자는 여러 Publisher가 발행하는 값 중 가장 최근에 발행된 Publisher의 값을 구독하여 방출합니다. 주로 최신 데이터만 필요할 때 사용됩니다.

import Combine

let subject1 = PassthroughSubject<String, Never>()
let subject2 = PassthroughSubject<String, Never>()
let subjects = PassthroughSubject<PassthroughSubject<String, Never>, Never>()

let switched = subjects.switchToLatest()

let cancellable = switched.sink { value in
    print("Received value: \(value)")
}

subjects.send(subject1)
subject1.send("Hello from subject1")

subjects.send(subject2)
subject2.send("Hello from subject2")
subject1.send("Another value from subject1") // 이 값은 무시됩니다.

Combine의 assign 연산자는 어떤 역할을 하나요?

assign 연산자는 Publisher가 발행하는 값을 특정 객체의 속성에 할당하는 데 사용됩니다. 주로 뷰 모델의 속성을 업데이트하거나 상태를 관리할 때 사용됩니다.

import Combine

class ViewModel: ObservableObject {
    @Published var text: String = ""
}

let viewModel = ViewModel()
let subject = PassthroughSubject<String, Never>()

let cancellable = subject.assign(to: \.text, on: viewModel)

subject.send("Hello, Combine!")

print(viewModel.text) // 출력: "Hello, Combine!"

Combine에서 메모리 관리를 어떻게 하나요? (예: AnyCancellable과 store 메서드)

Combine에서 메모리 관리는 주로 AnyCancellablestore 메서드를 사용하여 구독을 관리합니다. 구독을 취소하지 않으면 메모리 누수가 발생할 수 있으므로, 적절한 시점에 구독을 취소하는 것이 중요합니다.

  • AnyCancellable: AnyCancellable은 구독을 취소할 수 있는 객체로, 구독을 명시적으로 취소하거나, 객체가 메모리에서 해제될 때 자동으로 취소됩니다.

    var cancellable: AnyCancellable?
    
    let subject = PassthroughSubject<String, Never>()
    
    cancellable = subject.sink { value in
        print("Received value: \(value)")
    }
    
    subject.send("Hello")
    cancellable?.cancel()
  • store: store 메서드는 여러 AnyCancellable을 관리하기 위해 Set<AnyCancellable>에 구독을 저장합니다. 뷰 또는 뷰 모델의 생명주기와 함께 구독을 관리할 때 유용합니다.

    import Combine
    class ViewModel: ObservableObject {
        var cancellables = Set<AnyCancellable>()
    
        init() {
            let subject = PassthroughSubject<String, Never>()
            subject.sink { value in
                print("Received value: \(value)")
            }
            .store(in: &cancellables)
        }
    }
    
    let viewModel = ViewModel()

이렇게 하면 ViewModel이 해제될 때 모든 구독이 자동으로 취소됩니다

Last updated