Swift Combine中订阅者Sink被触发两次是否为预期行为?
问题:订阅者的Sink被触发两次是否属于预期行为?
测试代码如下:
func testExample() throws { let service = MockAPIService(isSuccessful: true) let sut = ContentViewModel(apiService: service) let fetchImagesExpectation = expectation(description: "Fetching images") sut.$images .sink { value in print("## count: ", value?.count) // fetchImagesExpectation.fulfill() // XCTAssertEqual(value?.count ?? 0, 5) } .store(in: &cancellables) sut.fetch() wait(for: [fetchImagesExpectation], timeout: 1) }
控制台输出:
## count: nil ## count: Optional(5)
ContentViewModel代码如下:
final class ContentViewModel: ObservableObject { @Published private (set) var images: [ImageModel]? private let apiService: NetworkingService private var cancellable: Set<AnyCancellable> init(apiService: NetworkingService = APIService()) { self.cancellable = Set<AnyCancellable>() self.apiService = apiService } func fetch() { apiService.fetchImages() .receive(on: RunLoop.main) .sink(receiveCompletion: { result in if case let .failure(error) = result { Logger.logError(error) } }, receiveValue: { [weak self] response in self?.handleRespose(response) }) .store(in: &cancellable) } private func handleRespose(_ response: SampleImagesResponse) { guard let responseImages = response.sample else { return } self.images = responseImages.compactMap { item in if let urlString = item.imageURL, let url = URL(string: urlString), let imageID = item.id, let title = item.description { return ImageModel(imageID: imageID, title: title, url: url) } return nil } } }
另外,当注释掉sut.fetch()时,控制台仍会打印## count: nil。
回答
这种情况属于预期行为,原因如下:
- 第一次Sink触发是因为
@Published属性的初始值发送:images属性的初始值是nil,当你订阅sut.$images时,Combine框架会立即将当前的属性值(也就是nil)发送给订阅者,这是@Published的默认行为——订阅会先收到当前的初始值,之后再接收属性的变化值。这也是注释掉sut.fetch()后仍会打印## count: nil的原因。 - 第二次Sink触发是因为
fetch()方法执行后的属性更新:调用sut.fetch()后,API请求成功返回数据,handleResponse方法会将处理后的数组赋值给images,@Published属性检测到值发生变化,就会将新的Optional(5)值发送给订阅者,从而触发第二次Sink。
综上,两次触发完全符合Combine中@Published的工作机制,是正常且预期的行为。
内容的提问来源于stack exchange,提问作者Maysam
相关产品推荐
相关产品推荐

