为什么我不能在这个支持 ReactiveSwift 的网络工作流中观察事件?

Why can't I observe events in this ReactiveSwift-enabled network workflow?

我正在训练自己使用 ReactiveSwift for networking and a good use case for this seemed to fetch photos for a location from the Google Places API for iOS

流程如下:

  1. 从 google 个地点 ID
  2. 获取 GMSPlacePhotoMetadata 的列表
  3. 对于每个元数据,获取一张图片
  4. 将所有图片连接成一个数组

我以我能想到的最好的 ReactiveSwift 方式编写了执行此工作流的代码(参见下面的代码)但是当我调用我的服务时,尽管所有 API 调用 Google 地方API都发了,我就不进入观察部分了。我觉得我从框架中遗漏了一些非常基本的东西,而且我一路上失去了一些观察者,但我无法指出问题所在。我们非常欢迎任何帮助。

我的服务代码

import Foundation
import ReactiveSwift
import GooglePlaces

struct GooglePlacesPhotoService {
    func findPlacePictures(googlePlaceID: String) -> SignalProducer<[UIImage], DataStoreError> {
        return findPlacePicturesMetadata(googlePlaceID: googlePlaceID)
            .map { (metadata) -> SignalProducer<UIImage, DataStoreError> in
                debugPrint("Mapping metadata to SignalProducer for metadata: ", metadata)
                return self.findPlacePicture(metadata: metadata)
            } // After mapping, we have a SignalProducer of SignalProducer<UIImage>
            .flatten(.merge) // After flatening, we get a single SignalProducer<UIImage>
            .reduce([], { (imageArray: [UIImage], newImage: UIImage) -> [UIImage] in
                debugPrint("Merging another picture")
                return imageArray + [newImage]
            }) // Now we have an array of UIImage
    }

    private func findPlacePicturesMetadata(googlePlaceID: String) -> SignalProducer<GMSPlacePhotoMetadata, DataStoreError> {
        return SignalProducer<GMSPlacePhotoMetadata, DataStoreError> { observer, disposable in
            GMSPlacesClient.shared().lookUpPhotos(forPlaceID: googlePlaceID) { photos, error in
                guard error == nil else { return observer.send(error: .externalError(error!)) }
                guard let photos = photos else { return }

                photos.results.forEach { metadata in
                    debugPrint("Sending metadata value: ", metadata)
                    observer.send(value: metadata)
                }
            }
        }
    }

    private func findPlacePicture(metadata: GMSPlacePhotoMetadata) -> SignalProducer<UIImage, DataStoreError> {
        return SignalProducer<UIImage, DataStoreError> { observer, disposable in
            let screenSize = UIScreen.main.bounds.size
            let screenScale = UIScreen.main.scale

            let myCallback: GMSPlacePhotoImageResultCallback = { image, error in
                guard error == nil else {
                    print("ERROR: couln't load picture for metadata \(metadata)")
                    observer.send(error: .externalError(error!))
                    return
                }

                guard let image = image else {
                    print("ERROR: empty image returned")
                    observer.send(error: .unknownExternalError)
                    return
                }

                debugPrint("Got 1 picture from metadata: ", metadata)
                observer.send(value: image)
            }

            GMSPlacesClient.shared().loadPlacePhoto(metadata,
                                                    constrainedTo: screenSize,
                                                    scale: screenScale,
                                                    callback: myCallback)
        }
    }
}

我的观察码

    googlePlaceIDProperty.signal
        .filter { [=11=].isPresent }
        .flatMap(.latest) { googlePlaceID in
            return GooglePlacesPhotoService().findPlacePictures(googlePlaceID: googlePlaceID!)
        }.observe { event in
            debugPrint("Signal event!") // I NEVER GET THERE
            switch event {
            case let .value(pictures):
                // Do stuff
            case let .failed(error):
                // Do stuff
            default:
                break
            }
    }

我的日志

"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006459950>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006459950>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006642130>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006642130>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006459950>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006642130>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Merging another picture"

The Event contract 表示它只会因失败、完成或中断事件而终止。因此,您需要确保在发送所有值后在 SignalProducer 闭包中调用 observer.sendCompleted()

The documentation for reduce says it returns a "producer that sends the final result as self completes", which means the results will never be sent along until that completed event happens. Basically, there's no way for it to know that it has collected all of the results unless your SignalProducers explicitly send a completed event to indicate that they have no more values to send. It is well illustrated on this graph.

所以在你的情况下,在 findPlacePicture 中,你应该在获得预期结果后调用 sendCompleted(),即:

observer.send(value: image)
observer.sendCompleted() // <- That's the line to add.

并在 findPlacePicturesMetadata 中:

photos.results.forEach { metadata in
    debugPrint("Sending metadata value: ", metadata)
        observer.send(value: metadata)
    }
observer.sendCompleted() // <- That's the line to add.