无法使用combine with SwiftUI从URL获取响应

无法使用combine with SwiftUI从URL获取响应,swiftui,combine,Swiftui,Combine,那是我的模范班 struct LoginResponse: Codable { let main: LoginModel } struct LoginModel: Codable { let success: Bool? let token: String? let message: String? static var placeholder: LoginModel { return LoginModel(succe

那是我的模范班

struct LoginResponse: Codable {
    let main: LoginModel
}

struct LoginModel: Codable {
    
    let success: Bool?
    let token: String?
    let message: String?
    
    static var placeholder: LoginModel {
        return LoginModel(success: nil, token: nil, message: nil)
    }
    
}
这是我的服务。我还有一个问题,我在这里使用了两个映射,但当尝试删除dataTaskPublisher中的map.data获取错误时。下面提到错误

实例方法“decode(type:decoder:)”要求“URLSession.DataTaskPublisher.Output”(aka)(数据:数据,响应:URLResponse))和“JSONDecoder.Input”(aka“data”)的类型等效

这是我的json回应

{
    "success": true,
    "token": "ed48aa9b40c2d88079e6fd140c87ac61fc9ce78a",
    "expert-token": "6ec84e92ea93b793924d48aa9b40c2d88079e6fd140c87ac61fc9ce78ae4fa93",
    "message": "Logged in successfully"
}

由于取消,您的发布服务器在呼叫上下文的正下方被销毁,因为您不保留对订户的引用

要解决此问题,您必须将对订户的引用保留在某个位置。最合适的变体位于某些成员属性中,但作为变体,它也可以是自包含的(如果符合您的目标),如

func loaginTask(){
var订户:是否可以取消?
订户=loginService.doLoginTask(用户名:“1234567890”,密码:“12345”)
.水槽(
receiveCompletion:{[subscriber]结果为
打印(“收到的完成:\(结果)”)

subscriber=nil/首先,您的错误源于您希望返回
AnyPublisher
,但您将响应映射为
。decode(键入:LoginResponse.self,decoder:JSONDecoder())
,它与您的json响应不匹配

第二次,我将使用基本授权作为URL请求的主体,因为它是用密码发送用户凭据的,密码必须受到保护。您有权访问服务器端吗?后端如何处理此post请求? 是授权还是内容类型?我会把这两个解决方案放在服务器端,尝试找到一个设置在服务器端的解决方案

您的登录模型必须与json响应匹配。我注意到他们的expertToken丢失:

struct LoginModel: Codable {

  let success: Bool
  let token: String
  let expertToken: String
  let message: String

  enum CodingKeys: String, CodingKey {
    case success
    case token
    case expertToken = "expert-token"
    case message
  }
}
因此,我将通过以下方式创建
LoginService
类:

final class LoginService {

  /// The request your use when the button is pressed.
  func logIn(username: String, password: String) -> AnyPublisher<LoginModel, Error> {

    let url = URL(string: "http://your.api.endpoints/")!
    let body = logInBody(username: username, password: password)
    let urlRequest = basicAuthRequestSetup(url: url, body: body)

    return URLSession.shared
      .dataTaskPublisher(for: urlRequest)
      .receive(on: DispatchQueue.main)
      .tryMap { try self.validate($0.data, $0.response) }
      .decode(
        type: LoginModel.self,
        decoder: JSONDecoder())
      .eraseToAnyPublisher()
  }

  /// The body for a basic authorization with encoded credentials.
  func logInBody(username: String, password: String) -> String {

    let body = String(format: "%@:%@",
                      username,
                      password)

    guard let bodyData = body.data(using: .utf8) else { return String() }

    let encodedBody = bodyData.base64EncodedString()
    return encodedBody
  }

  /// The authorization setup
  func basicAuthRequestSetup(url: URL, body: String) -> URLRequest {

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("Basic \(body)",
                        forHTTPHeaderField: "Authorization")

    return urlRequest
  }

  /// Validation of the Data and the response.
  /// You can handle response with status code for more precision.
  func validate(_ data: Data, _ response: URLResponse) throws -> Data {
    guard let httpResponse = response as? HTTPURLResponse else {
      throw NetworkError.unknown
    }
    guard (200..<300).contains(httpResponse.statusCode) else {
      throw networkRequestError(from: httpResponse.statusCode)
    }
    return data
  }

  /// Handle the status code errors to populate to user.
  func networkRequestError(from statusCode: Int) -> Error {
    switch statusCode {
    case 401:
      return NetworkError.unauthorized
    default:
      return NetworkError.unknown
    }
  }

  /// Define your different Error here that can come back from
  /// your backend.
  enum NetworkError: Error, Equatable {

    case unauthorized
    case unknown
  }
}
然后,我将创建一个ViewModel来处理将在视图中传递的逻辑

final class OnboardingViewModel: ObservableObject {

  var logInService = LoginService()

  var subscriptions = Set<AnyCancellable>()

  func logIn() {
    logInService.logIn(username: "Shubhank", password: "1234")
      .sink(receiveCompletion: { completion in
              print(completion) },
            receiveValue: { data in
              print(data.expertToken) })  // This is your response
      .store(in: &subscriptions)
  }
}

谢谢,我明白你的意思,试着这么做很抱歉打扰了你,但我还是做不到。谢谢你,兄弟,这是工作。你救了我一天。再次谢谢。
struct LoginModel: Codable {

  let success: Bool
  let token: String
  let expertToken: String
  let message: String

  enum CodingKeys: String, CodingKey {
    case success
    case token
    case expertToken = "expert-token"
    case message
  }
}
final class LoginService {

  /// The request your use when the button is pressed.
  func logIn(username: String, password: String) -> AnyPublisher<LoginModel, Error> {

    let url = URL(string: "http://your.api.endpoints/")!
    let body = logInBody(username: username, password: password)
    let urlRequest = basicAuthRequestSetup(url: url, body: body)

    return URLSession.shared
      .dataTaskPublisher(for: urlRequest)
      .receive(on: DispatchQueue.main)
      .tryMap { try self.validate($0.data, $0.response) }
      .decode(
        type: LoginModel.self,
        decoder: JSONDecoder())
      .eraseToAnyPublisher()
  }

  /// The body for a basic authorization with encoded credentials.
  func logInBody(username: String, password: String) -> String {

    let body = String(format: "%@:%@",
                      username,
                      password)

    guard let bodyData = body.data(using: .utf8) else { return String() }

    let encodedBody = bodyData.base64EncodedString()
    return encodedBody
  }

  /// The authorization setup
  func basicAuthRequestSetup(url: URL, body: String) -> URLRequest {

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("Basic \(body)",
                        forHTTPHeaderField: "Authorization")

    return urlRequest
  }

  /// Validation of the Data and the response.
  /// You can handle response with status code for more precision.
  func validate(_ data: Data, _ response: URLResponse) throws -> Data {
    guard let httpResponse = response as? HTTPURLResponse else {
      throw NetworkError.unknown
    }
    guard (200..<300).contains(httpResponse.statusCode) else {
      throw networkRequestError(from: httpResponse.statusCode)
    }
    return data
  }

  /// Handle the status code errors to populate to user.
  func networkRequestError(from statusCode: Int) -> Error {
    switch statusCode {
    case 401:
      return NetworkError.unauthorized
    default:
      return NetworkError.unknown
    }
  }

  /// Define your different Error here that can come back from
  /// your backend.
  enum NetworkError: Error, Equatable {

    case unauthorized
    case unknown
  }
}
/// Classic body for content type.
/// Keys must match the one in your server side.
func contentTypeBody(username: String, password: String) -> [String: Any] {
  [
    "username": username,
    "password": password
  ] as [String: Any]
}

/// Classic Content-Type but not secure. To avoid when having
/// passwords.
func contentTypeRequestSetup(url: URL,
                      body: [String: Any]) -> URLRequest {

  var urlRequest = URLRequest(url: url)
  urlRequest.httpMethod = "POST"
  urlRequest.setValue("application/json",
                      forHTTPHeaderField: "Content-Type")
  urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)

  return urlRequest
}
final class OnboardingViewModel: ObservableObject {

  var logInService = LoginService()

  var subscriptions = Set<AnyCancellable>()

  func logIn() {
    logInService.logIn(username: "Shubhank", password: "1234")
      .sink(receiveCompletion: { completion in
              print(completion) },
            receiveValue: { data in
              print(data.expertToken) })  // This is your response
      .store(in: &subscriptions)
  }
}
struct ContentView: View {

  @ObservedObject var viewModel = OnboardingViewModel()

  var body: some View {
    Button(action: { viewModel.logIn() }) {
      Text("Log In")
    }
  }
}