Deferred는 클로저 안에 정의된 Publisher가 실제로 구독이 되기 전까지 생성을 지연시키는 Publisher에요.

특징

사용

let deferredPublisherWithDelay = Deferred {
		return Just([1,2,3,4,5]) // 네트워킹 작업을 가정한 Publisher
    .delay(for: .seconds(1), scheduler: RunLoop.main)
}

deferredPublisherWithDelay
  .sink { values in
    print(values)
  }
  .store(in: &cancellables)

위 예제에서 Deferred의 내부 생성된 Publisher Just([1,2,3,4,5])sink 메서드를 통해서 구독이 이루어 질때 인스턴스가 생성돼요.


동적 Publisher 생성

Deferred는 Publisher을 주입하여 사용하기 때문에 상황에 따라 동적으로 다른 Publisher를 생성할 수 있습니다.

여기서는 인터넷 연결상태에 따라 Local 또는 원격 API 에서 데이터를 가져오는 Publisher를 구독하는 코드를 작성해보겠습니다.

// 로드될 구조체
struct UserProfile {
  var userId: Int
  var profileUrl: URL
}

// 로컬 데이터 로드
func fetchUserFromLocal() -> AnyPublisher<Result<UserProfile, Error>, CustomError> {
  return Just(.success(UserProfile(userId: -1, profileUrl: URL(string: "<https://www.mock.placeholder>")!)))
    .setFailureType(to: CustomError.self)
    .eraseToAnyPublisher()
}

// 원격 데이터 로드
func fetchUserFromRemote(userId: Int) -> AnyPublisher<Result<UserProfile, Error>, CustomError> {
  // 예제를 위한 가상의 URL
  let url = URL(string: "<https://example.com/users/\\(userId)>")!
  return Future<Result<UserProfile, Error>, Never> { promise in
    promise(.success(.success(UserProfile(userId: userId, profileUrl: url))))
  }
	.setFailureType(to: CustomError.self)
  .eraseToAnyPublisher()
}

// 인터넷 연결여부 체크 가정
func isConnectedToInternet() -> Bool { 
	return Bool.random()
}

// 인터넷 연결여부에 따라 다른 Publisher 적용
let dynamicPublisher = Deferred { 
	isConnectedToInternet() ? fetchUserFromRemote(userId: 1) : fetchUserFromLocal()
}

// Deferred 구독
dynamicPublisher
  .sink { completion in
    switch completion {
    case .finished:
      print("finished")
    case .failure(let error):
      print("error occured: \\(error)")
    }
  } receiveValue: { result in
    switch result {
    case .success(let userProfile):
      print(userProfile)
    case .failure(let error):
      print("custom error occured: \\(error)")
    }
  }
  .store(in: &cancellables)