Swift:链接到异步函数并等待执行另一个函数

时间:2020-09-24 12:47:08

标签: ios swift xcode

我不太了解等待从任何异步调用(任何:网络,计时器,任何异步执行的调用,而我不得不等待执行另一个调用)并在另一个同步中使用它的模式位置(如何链接到操作。我看到了使用Flatmap的示例,它们涉及2个Web调用。在这种情况下,我必须从Web上检索数据(会话ID),并将其保存以供进一步使用(持续一个小时)我读到了有关Operations,DispatchGroups的信息,但并没有让他们在这里工作。 我有一个简单的类,它从Web服务获取数据,我必须等到其下载并保存。

import Foundation
import Combine
import CoreData

struct SessionId:Codable {
    
    let key:String?
    let dateTime:Date?
    
}

class ColppyModel {
    
    var session:SessionId?
        
    var key:String?
    var cancellable:AnyCancellable?
    
    init() {
        print("saving")
        let sess = SessionId(key: "1", dateTime: DateComponents(calendar:Calendar(identifier: .gregorian), year:2020, month:1, day:1).date)
        guard let data = try? JSONEncoder().encode(sess) else {return}
        let defaults = UserDefaults.standard
        defaults.set(data, forKey: "sessionIdKey")
        print("Saved \(sess)")
    }
    
     func getSessionKey(){
        
        let requestData = ColppyAPIRequests.createSessionIdRequestData()
        cancellable = ColppyAPI.sessionKeyRequest(sessionKeyJsonData: requestData)
            .replaceError(with: nil)
            .map{$0?.response?.data?.claveSesion}
            .receive(on: DispatchQueue.global(qos: .userInteractive))
            .sink(receiveValue: { (clave) in
                let data = try! JSONEncoder().encode(SessionId(key: clave!, dateTime: Date()))
                UserDefaults.standard.set(data, forKey: "sessionIdKey")
            })
    }
    
    
     func getSessionIDFromUserDefaults() -> SessionId? {
        let defaults = UserDefaults.standard
        let data = defaults.data(forKey: "sessionIdKey")
        guard let safeData = data else { return nil }
        guard let sessionId = try? JSONDecoder().decode(SessionId.self, from: safeData) else {return nil}
        return sessionId
    }
    
}

我以这种方式在SwiftUI View中使用它

import SwiftUI

struct ContentView: View {
    
    let ss = ColppyModel()
    
    var body: some View {
        Text("Press")
            .onTapGesture {
                self.getInvoices()
        }
        
    }
    
    private  func getInvoices() {
        let id = ss.getSessionIDFromUserDefaults()
        print(id)

        ss.getSessionKey()
        print(ss.getSessionIDFromUserDefaults())
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        return ContentView()
    }
}

我第一次单击时得到

enter image description here

第二次单击我得到

enter image description here

正确的项目已保存。

我该如何等待直到从服务器中检索到数据(在这种情况下为字符串)并保存以正确从商店中获取数据?

真的,我不太了解组合模式。

非常感谢

1 个答案:

答案 0 :(得分:1)

如果您只需要在此异步请求完成后执行某些操作,则在此处使用合并并没有真正的好处(在我看来)-您可以仅使用常规的旧回调:

func getSessionKey(@escaping completion: () -> Void) {

   // ...
   .sink(receiveValue: { (clave) in
       let data = try! JSONEncoder().encode(SessionId(key: clave!, dateTime: Date()))
       UserDefaults.standard.set(data, forKey: "sessionIdKey")

       completion()
   })

}

(我只是复制了您的代码,但我不鼓励使用try!

然后,您可以(使用结尾的闭包语法)

ss.getSessionKey() {
  print(ss.getSessionIDFromUserDefaults())
}

如果您坚持使用合并,则getSessionKey需要返回发布者,而不是sink设置值。假设发布者发出一个Void值来表示完成:

func getSessionKey() -> AnyPublisher<Void, Never> {
   // ...
   return ColppyAPI
       .sessionKeyRequest(sessionKeyJsonData: requestData)
       .map { $0.response?.data?.claveSession }
       .replaceError(with: nil)

       // ignore nil values
       .compactMap { $0 } 

       // handle side-effects
       .handleEvents(receiveOutput: { 
          let data = try! JSONEncoder().encode(SessionId(key: $0, dateTime: Date()))
          UserDefaults.standard.set(data, forKey: "sessionIdKey")
       })            
}

这现在返回一个发布者,您可以在其他地方订阅该发布者(并在其中存储可取消的内容):

ss.getSessionKey()
  .receive(on: ...)
  .sink { 
      print(ss.getSessionIDFromUserDefaults())
  }
  .store(in: &cancellables)

当然,现在您需要弄清楚可取消对象的存储位置,如何在不可变的视图中实现此目的不是立即显而易见的(您需要将其设置为@State属性)。 / p>

总而言之,这不是学习合并模式的好例子-仅使用caallbacks。