使用2x Publisher快速组合URLSession检索数据集/照片 [英] Swift Combine URLSession retrieving Dataset/Photos using 2x Publishers

查看:95
本文介绍了使用2x Publisher快速组合URLSession检索数据集/照片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的大部分功能都可以正常工作并返回我想要的功能.但是,在将 photos 数组放入 response 中并将它们分配给适当的 employees 时,我有点放屁.能够渲染它们.这是怎么回事:

I have majority of the functionality working and returning exactly what I want. However, I'm having bit of a brain fart when it comes to taking the photos array in the response and assigning them to appropriate employees to be able to render them. Here's what's going on:

  1. 有4种可编码结构:响应公司 Employee ProfileImages . Response 是API返回的主要对象,然后解码为具有 [Employee] 数组的 Company ,每个都有3x ProfileImages(大小不一)
  2. 有一个 companyPublisher 可以获取 company 的详细信息以及一组 employees
  3. 然后有一个 photosPublisher ,它从上一步中获取 employees 数组,并对它们进行排序,以便能够检索其 profileImages.large 个人资料图像
  4. 最后,我有一个 Publishers.Zip(companyPublisher,photosPublisher),用于设置发布者的 .sink()以在完成请求的所有内容后做出响应
  1. There are 4x Codable structs: Response, Company, Employee, and ProfileImages. Response is the main object returned by the API and then decoded into Company having an array of [Employee], each have 3x ProfileImages (small, medium, and large in size)
  2. There's a companyPublisher that fetches the company details along with an array of employees
  3. Then there's a photosPublisher which takes employees array from the previous step and sequences them to be able to retrieve their profileImages.large profile image
  4. At last, I have a Publishers.Zip(companyPublisher, photosPublisher) that sets up the publisher's .sink() to respond with completion once everything requested has been fetched.

有人可以建议我需要采取哪些适当的步骤,才能将正确的员工形象分配给实际员工?我当时在考虑在 Employee 可编码结构中设置一个可选的 UIImage 类型 property ,但仍不确定如何分配合适的将来会反对该雇员.

Can someone advise what would be the appropriate steps I need to take to be able to assign the correct employee image to the actual employee? I was thinking about setting up an optional UIImage type property inside the Employee codable struct but am still unsure on how I would go about assigning the appropriate Future object to that employee.

任何帮助将不胜感激.预先感谢!

Any help would be greatly appreciated. Thanks in advance!

Response.JSON :

{
  "success": true,
  "company": {
    "id": 64,
    "name": "XYZ (Birmingham, AL)",
    "enabled": true
  },
  "employees": [{
    "id": 35,
    "name": "Chad Hughes",
    "email": "chad.hughes@company.com",
    "profileImages": {
      "small": "https://via.placeholder.com/150/09f/fff.png",
      "medium": "https://via.placeholder.com/300/09f/fff.png",
      "large": "https://via.placeholder.com/600/09f/fff.png"
    }
  }, {
    "id": 36,
    "name": "Melissa Martin",
    "email": "melissa.martin@company.com",
    "profileImages": {
      "small": "https://via.placeholder.com/150/F2A/fff.png",
      "medium": "https://via.placeholder.com/300/F2A/fff.png",
      "large": "https://via.placeholder.com/600/F2A/fff.png"
    }
  }]
}

Models.swift (可编码结构):

struct Response: Codable {
  let success: Bool
  let company: Company
  let employees: [Employee]
  let message: String?
}

struct Company: Codable, Identifiable {
  let id: Int
  let name: String
  let enabled: Bool
}

struct Employee: Codable, Identifiable {
  let id: Int
  let name: String
  let email: String
  let profileImages: ProfileImage
  let profileImageToShow: SomeImage?
}

struct SomeImage: Codable {
  let photo: Data
  init(photo: UIImage) {
    self.photo = photo.pngData()!
  }
}

struct ProfileImage: Codable {
  let small: String
  let medium: String
  let large: String
}

CompanyDetails.swift :

class CompanyDetails: ObservableObject {
  private let baseURL = "https://my.api.com/v1"
  
  @Published var company: Company = Company()
  @Published var employees: [Employee] = []
  
  var subscriptions: Set<AnyCancellable> = []
  
  func getCompanyDetails(company_id: Int) {
    let url = URL(string: "\(baseURL)/company/\(company_id)")
    
    // Company Publisher that retrieves the company details and its employees
    let companyPublisher = URLSession.shared.dataTaskPublisher(for url: url)
      .map(\.data)
      .decode(type: Response.self, decoder: JSONDecoder())
      .eraseToAnyPublisher()
    
    // Photo Publisher that retrieves the employee's profile image in large size
    let photosPublisher = companyPublisher
      .flatMap { response -> AnyPublisher<Employee, Error> in
        Publishers.Sequence(sequence: response.employees)
          .eraseToAnyPublisher()
      }
      .flatMap { employee -> AnyPublisher<UIImage, Error> in
        URLSession.shared.dataTaskPublisher(for url: URL(string: employee.profileImages.large)!)
          .compactMap { UIImage(data: $0.data) }
          .mapError { $0 as Error }
          .eraseToAnyPublisher()
      }
      .collect()
      .eraseToAnyPublisher()
    
    // Zip both Publishers so that all the retrieved data can be .sink()'d at once
    Publishers.Zip(companyPublisher, photosPublisher)
      .receive(on: DispatchQueue.main)
      .sink(
        receiveCompletion: { completion in
          print(completion)
        },
        receiveValue: { company, photos in
          print(company)
          print(photos)
        }
      )
      .store(in: &subscriptions)
  }
}

推荐答案

您已经快到了,但是您需要"zip"在内部(嵌套)级别,即在 flatMap 内:

You're almost there, but you need to "zip" at an inner (nested) level, ie. inside the flatMap:

let employeesPublisher = companyPublisher
   .flatMap { response in
      response.employees.publisher.setFailureType(Error.self)
   }
   .flatMap { employee -> AnyPublisher<(Employee, UIImage), Error> in

      let profileImageUrl = URL(string: employee.profileImages.large)!

      return URLSession.shared.dataTaskPublisher(for url: profileImageUrl)
          .compactMap { UIImage(data: $0.data) }
          .mapError { $0 as Error }

          .map { (employee, $0) } // "zip" here into a tuple

          .eraseToAnyPublisher()

   }
   .collect()

现在您有了一个由雇员和个人资料图像组成的元组数组.同样,您也可以检索所有配置文件图像,例如,使用 Publishers.Zip3 .

Now you have and array of tuples of employee and the profile image. Similarly, you could also retrieve all the profile images, for example, by using Publishers.Zip3.

编辑

如果要更新 employee 值而不是返回元组,则可以返回已更新的雇员:

If you want to update the employee value instead of returning a tuple, you can instead return the updated employee:

// ...
   .map {
      var employeeCopy = employee
      employeeCopy.profileImageToShow = SomeImage(photo: $0)
      return employeeCopy
   }
// ...

这为您提供了一组具有 profileImageToShow 属性集的员工,您可以根据需要使用原始响应对 .zip 进行设置:

What this gives you is an array of employees with profileImageToShow property set, which you can .zip with the original response, as you wanted:

Publishers.Zip(companyPublisher, employeesPublisher)
   .receive(on: DispatchQueue.main)
   .sink { (response, employees) in 
      self.company = response.company
      self.employees = employees
   }
   .store(in: &subscriptions)

这篇关于使用2x Publisher快速组合URLSession检索数据集/照片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆