使用 2x Publishers 快速组合 URLSession 检索数据集/照片

Pau*_*vis 5 ios swift urlsession combine

我的大部分功能都在工作,并且返回的正是我想要的。photos然而,当涉及到将数组放入response并将它们分配给适当的位置employees以便能够渲染它们时,我有点脑残。这是发生的事情:

  1. 有 4x 可编码结构:ResponseCompanyEmployeeProfileImagesResponse是 API 返回的主要对象,然后解码为Company一个数组[Employee],每个数组有 3x ProfileImages(小、中、大尺寸)
  2. 有一个companyPublisher可以获取company详细信息以及一系列employees
  3. 然后有一个从上一步中photosPublisher获取employees数组并对它们进行排序以便能够检索其profileImages.large个人资料图像
  4. 最后,我有一个Publishers.Zip(companyPublisher, photosPublisher)设置发布者.sink()在获取所有请求的内容后响应完成。

有人可以建议我需要采取哪些适当的步骤才能将正确的员工图像分配给实际员工?我正在考虑在可编码结构中设置一个可选UIImage类型,但仍然不确定如何将适当的 Future 对象分配给该员工。propertyEmployee

任何帮助将不胜感激。提前致谢!

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"
    }
  }]
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

New*_*Dev 2

你已经快到了,但是你需要在内部(嵌套)级别“压缩”,即。在 - 的里面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()
Run Code Online (Sandbox Code Playgroud)

现在您拥有员工元组和个人资料图像的数组。同样,您也可以检索所有个人资料图像,例如使用Publishers.Zip3.

编辑

如果您想更新employee值而不是返回元组,则可以返回更新后的员工:

// ...
   .map {
      var employeeCopy = employee
      employeeCopy.profileImageToShow = SomeImage(photo: $0)
      return employeeCopy
   }
// ...
Run Code Online (Sandbox Code Playgroud)

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

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