Swift 结合 URLSession 检索 Dataset/Photos 使用 2x Publishers
Swift Combine URLSession retrieving Dataset/Photos using 2x Publishers
我的大部分功能都能正常工作并返回我想要的内容。但是,当涉及到 response
中的 photos
数组并将它们分配给适当的 employees
以便能够渲染它们时,我有点脑残。这是发生了什么:
- 有 4 个 Codable 结构:
Response
、Company
、Employee
和 ProfileImages
。 Response
是API返回的主对象,然后解码成Company
有一个[Employee]
的数组,每个有3xProfileImages
(小,中,和大)
- 有一个
companyPublisher
可以获取 company
详细信息以及 employees
的数组
- 然后是一个
photosPublisher
,它从上一步中获取 employees
数组并对它们进行排序,以便能够检索他们的 profileImages.large
个人资料图像
- 最后,我有一个
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: [=14=].data) }
.mapError { [=14=] 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)
}
}
你快到了,但你需要在内部(嵌套)级别“压缩”,即。在 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: [=10=].data) }
.mapError { [=10=] as Error }
.map { (employee, [=10=]) } // "zip" here into a tuple
.eraseToAnyPublisher()
}
.collect()
现在你有了员工和个人资料图片的元组数组。同样,您也可以检索所有个人资料图片,例如,使用 Publishers.Zip3
.
编辑
如果您想更新 employee
值而不是 return 元组,您可以 return 更新后的员工:
// ...
.map {
var employeeCopy = employee
employeeCopy.profileImageToShow = SomeImage(photo: [=11=])
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)
我的大部分功能都能正常工作并返回我想要的内容。但是,当涉及到 response
中的 photos
数组并将它们分配给适当的 employees
以便能够渲染它们时,我有点脑残。这是发生了什么:
- 有 4 个 Codable 结构:
Response
、Company
、Employee
和ProfileImages
。Response
是API返回的主对象,然后解码成Company
有一个[Employee]
的数组,每个有3xProfileImages
(小,中,和大) - 有一个
companyPublisher
可以获取company
详细信息以及employees
的数组
- 然后是一个
photosPublisher
,它从上一步中获取employees
数组并对它们进行排序,以便能够检索他们的profileImages.large
个人资料图像 - 最后,我有一个
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: [=14=].data) }
.mapError { [=14=] 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)
}
}
你快到了,但你需要在内部(嵌套)级别“压缩”,即。在 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: [=10=].data) }
.mapError { [=10=] as Error }
.map { (employee, [=10=]) } // "zip" here into a tuple
.eraseToAnyPublisher()
}
.collect()
现在你有了员工和个人资料图片的元组数组。同样,您也可以检索所有个人资料图片,例如,使用 Publishers.Zip3
.
编辑
如果您想更新 employee
值而不是 return 元组,您可以 return 更新后的员工:
// ...
.map {
var employeeCopy = employee
employeeCopy.profileImageToShow = SomeImage(photo: [=11=])
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)