【问题标题】:Swift Combine URLSession retrieving Dataset/Photos using 2x Publishers使用 2x Publishers 快速组合 URLSession 检索数据集/照片
【发布时间】:2020-07-14 12:45:41
【问题描述】:

我的大部分功能都可以正常工作并返回我想要的东西。但是,当谈到在response 中获取photos 数组并将它们分配给适当的employees 以便能够渲染它们时,我有点脑残。这是发生了什么:

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

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

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

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)
  }
}

【问题讨论】:

  • @NewDev 是的,这确实帮助我最初制定我的算法以输出正确的响应......我正在努力!现在我只是停留在如何将适当的嵌套未来照片对象(每个 Employee 的 ProfileImage.large)关联到它们的相关数组元素(作为公司返回的员工数组中的 Employee)

标签: ios swift combine urlsession


【解决方案1】:

您快到了,但您需要在内部(嵌套)级别“压缩”,即。在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

编辑

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

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

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

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

【讨论】:

  • 好的,使用这个方法,现在我的输出如下 -> ([success, company, [employee]], [[employee, UIImage]])。拥有两次[Employee] 数组似乎仍然是多余的。当你说“zip here into a tuple”时,你的意思是:.map { Publishers.Zip(companyPublisher, (employee, $0)) }?我想我很难理解如何在 Publisher 内部进行 Zip 压缩。所有这些异步/未来的东西都很棒,但它肯定需要我们大脑处理逻辑的方式(通常是顺序的)进行范式转变。
  • 在我的版本中,不需要您在问题中使用的最终 Zip。如果您执行employeesPublisher.sink { ... },您将获得员工图像元组数组。
  • 配置文件UIImage对象存储在哪里?我只是以一个元组为例来展示如何将员工与检索到的个人资料图像相关联
  • 我用Employee 中的一个新的可选属性更新了我的问题,该属性称为profileImageToShow,类型为SomeImage,它本质上是我们从API 中提取的UIImage 的包装器。跨度>
  • 好的...那么你可以只返回更新的员工,而不是元组
猜你喜欢
  • 2017-08-20
  • 1970-01-01
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多