2017-05-17 1 views
0

ReactiveSwift 1.1.1、MVVM + Flow Coordinatorパターン、Firebaseをバックエンドとして使用して、Swift 3のiOSアプリケーションを開発しています。私は最近、FRPに順応し始めましたが、私は既存のコードベースに新しい機能を統合する方法を見つけようとしています。ReactiveSwiftとFirebase非同期メソッド呼び出しを使用してSignalProducerを処理する方法は?

たとえば、私のモデルではFirebaseの非同期メソッドを使用してWebからサムネイルをダウンロードしています。サムネイルがダウンロードされている場合は、ViewModelクラスからサブスクライブしてUIを更新するようにしてください。SignalProducer<Content, NoError>をご覧ください。

// field to be used from the view-models to observe 
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in 
    // TODO: send next content via completion below 
} 

// thumbnail download method 
public func findThumbnail(bucketId: String, contentId: String) { 
    guard let userId = userService.getCurrentUserId() else { 
     debugPring("Error id") 
     return 
    } 

    let ref = self.storageThumbnail.reference() 
    let contentRef = ref 
     .child(userId) 
     .child(bucketId) 
     .child(FirebaseConstants.pathImages) 
     .child("\(contentId).jpg") 

    contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in 
     guard let data = data else { 
      debugPrint("Error download") 
      return 
     } 
     let content = Image(data: data) 
     content.id = contentId 
     content.userId = userId 
     content.bucketId = bucketId 

     // TODO: emit signal with content 
     // How to send the content via the SignalProducer above? 
    }) 
} 

私は(observer, disposable)タプルを受信するSignal<Content, NoError>.pipe()メソッドを使用して、私はそれがFirebaseコールバックを形成アクセスするために、プライベート・グローバルフィールドとしてオブザーバーを保存したのに対し、私はまた、Signal<Content, NoError>と同様のものを試してみました。

質問:

は、これは正しいアプローチですか私は何かが足りないのですか?

完了時にコンテンツオブジェクトを放出するにはどうすればよいですか?

UPDATE:痛みのいくつかの時間後

、私は信号を発するようにしてのviewmodelsから購読するSingalProducerを設計する方法を見つけました。

たぶん、次のコードスニペットはまた、他の人を助ける:

// model protocol 
import ReactiveSwift 
import enum Result.NoError 

public protocol ContentService { 
    func findThumbnail(bucketId: String, contentId: String) 
    var thumbnailContentProducer: SignalProducer<Content, NoError> { get } 
} 


// model implementation using firebase 
import Firebase 
import FirebaseStorage 
import ReactiveSwift 

public class FirebaseContentService: ContentService { 

    // other fields, etc. 
    // ... 

    private var thumbnailContentObserver: Observer<Content, NoError>? 
    private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>? 
    var thumbnailContentProducer: SignalProducer<Content, NoError> { 
     return thumbnailContentSignalProducer! 
    } 

    init() { 
     thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in 
      self.thumbnailContentObserver = observer 
     } 
    } 

    func findThumbnail(bucketId: String, contentId: String) { 
     guard let userId = userService.getCurrentUserId() else { 
      // TODO handle error 
      return 
     } 

     let ref = self.storageThumbnail.reference() 
     let contentRef = ref 
      .child(userId) 
      .child(bucketId) 
      .child(FirebaseConstants.pathImages) 
      .child("\(contentId).jpg") 

     contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in 
      guard let data = data else { 
       // TODO handle error 
       return 
      } 
      let content = Image(data: data) 
      content.id = contentId 
      content.userId = userId 
      content.bucketId = bucketId 
      // emit signal 
      self.thumbnailContentObserver?.send(value: content) 
     }) 
    } 
} 


// usage from a ViewModel 
contentService.thumbnailContentProducer 
    .startWithValues { content in 
     self.contents.append(content) 
    } 

たぶん誰かが上記のコードを検証し、これはそれを行うための正しい方法であると言うことができます。

+0

明確にするには、究極的には、 'findThumbnail'が呼び出されるたびに値を送信する1つのシグナルが必要ですか? – jjoelson

+0

@jjoelsonはい、今まで私は独自の解決策を見つけ、上記の記事を編集しました。ソリューションは大丈夫ですか? –

+0

あなたのソリューションは少し壊れるようです。 'init'のクロージャは、クライアントが' start'を呼び出すたびに呼び出されます。これは 'thumbnailContentObserver'がいつでも切り替えることができることを意味します。これは' Content'値がいつでも別の信号に迂回できることを意味します。私の解決策は、すべての 'Content'値が単一の' Signal'に来ることを保証します。必要に応じて複数回購読することができます。 – jjoelson

答えて

0

Signalpipeを使用して見ていたとき、あなたは正しい道のりだったと思います。要点は、サムネイルリクエストごとに新しいSignalProducerを作成する必要があることです。これらのリクエストすべてを1つの結果信号に結合する方法が必要です。私は(これは未テストコードであることに注意が、それは全体のアイデアを得る必要があります)、このような何かを考えていた。このようなReactiveExtensionsProviderを使用

class FirebaseContentService { 
    // userService and storageThumbnail defined here 
} 

extension FirebaseContentService: ReactiveExtensionsProvider { } 

extension Reactive where Base: FirebaseContentService { 
    private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> { 
     return SignalProducer<Content, ContentError> { (observer, disposable) in 
      guard let userId = self.base.userService.getCurrentUserId() else { 
       observer.send(error: ContentError.invalidUserLogin) 
       return 
      } 

      let ref = self.base.storageThumbnail.reference() 
      let contentRef = ref 
       .child(userId) 
       .child(bucketId) 
       .child(FirebaseConstants.pathImages) 
       .child("\(contentId).jpg") 

       contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in 
       guard let data = data else { 
        observer.send(error: ContentError.contentNotFound) 
        return 
       } 
       let content = Image(data: data) 
       content.id = contentId 
       content.userId = userId 
       content.bucketId = bucketId 

       observer.send(value: content) 
       observer.sendCompleted() 
      }) 
     } 
    } 
} 

class ThumbnailProvider { 
    public let thumbnailSignal: Signal<Content, NoError> 

    private let input: Observer<(bucketId: String, contentId: String), NoError> 

    init(contentService: FirebaseContentService) { 
     let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe() 

     self.input = observer 
     self.thumbnailSignal = signal 
      .flatMap(.merge) { param in 
       return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId) 
        .flatMapError { error in 
         debugPrint("Error download") 
         return SignalProducer.empty 
        } 
      } 
    } 

    public func findThumbnail(bucketId: String, contentId: String) { 
     input.send(value: (bucketId: bucketId, contentId: contentId)) 
    } 
} 

reactiveプロパティを使用して、既存の機能への反応性のAPIを追加する慣用的な方法です。

実際の要求コードは、に限定され、要求ごとにSignalProducerが作成されます。エラーはここに渡され、NoErrorへの処理と変換は後で行われます。

findThumbnailsは、ちょうどbucketIdcontentIdを入力し、それを入力観測可能に送信します。

thumbnailSignalの構成はinitで、魔法が発生する場所です。 bucketIdcontentIdを含むタプルである各入力は、flatMapによってリクエストに変換されます。 .merge戦略は、要求が完了した順番にできるだけ早くサムネイルが送信されることを意味します。サムネイルが要求されたのと同じ順序で返されるようにする場合は、.concatを使用できます。

flatMapErrorは潜在的なエラーが処理される場所です。この場合、単に「エラー・ダウンロード」と表示され、何もしません。

+0

ありがとうございました!上記のコードは私の日を救った。私はいくつかの小さな修正を加え、オリジナルの投稿を更新しました。 –

+0

OK、私は編集を承認しましたが、なぜあなたが 'thumbnailSignalRef'を変更可能でオプションにしたいのか分かりません。 'init'で一度だけ設定する必要があるので、変更することはできません。 – jjoelson

+0

問題は、 'self.getThumbnailContentSignalProducer'のためにコンパイルエラーが発生するため、' thumbnailSignal'をあなたが記述した方法で初期化することができないということです:変数 'self.thumbnailSignal'を初期化する前に使用しました。私はプロトコルでgetterプロパティを定義し、initコンストラクタの実装クラスを介して定義したいと思いますが、私はそのラインをweakからunownedなどのすべての方法で折り曲げようとしましたが、それをよりよく定義することはできません。あなたは何か考えていますか? –

関連する問題