对 Unsplash api 的异步请求无法正常工作
async request to Unsplash api not working correctly
我的 swift 包 UnsplashSwiftUI 遇到了一些问题
在 WWDC 之前,我遇到了一些问题导致我的视图重新加载(正如您在主分支上看到的那样)但是当 async/await 宣布时,这似乎是我的包的绝佳机会。
我正在开发分支上 async/await 开发包。
但是,我现在在处理异步 API 请求时遇到了一些问题。
这是我的最小可重现示例,我从我的异步函数 getURL() 的 catch 块中得到打印错误 'Failed to fetch image'。我还尝试在内部使用异步调用任务。
//From this
.task {
await getURL()
}
//To this
.task {
async {
await getURL()
}
}
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack {
UnsplashRandom(clientId: "TSozaArCYtCWcXnnUkh4KvKJ5ZfmVOn_FYbIVVn76Ew")
.frame(width: 500, height: 500)
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
import SwiftUI
@available(iOS 15, OSX 12, *)
public struct UnsplashRandom: View {
//MARK: Parameters
//Required parameters
var clientId: String //Unsplash API access key
@State private var unsplashData: UnsplashData? = nil
@State private var requestURL: URL? = nil
//MARK: Init
public init(clientId: String) {
self.clientId = clientId
let url = URL(string: "https://api.unsplash.com/")!
guard var components = URLComponents(url: url.appendingPathComponent("photos/random"), resolvingAgainstBaseURL: true)
else { fatalError("Couldn't append path component")}
components.queryItems = [URLQueryItem(name: "client_id", value: clientId)]
_requestURL = State(initialValue: components.url!)
}
//MARK: Body
public var body: some View {
//MARK: Main View
ZStack(alignment: .bottomTrailing) {
//MARK: Remote Image
AsyncImage (url: URL(string: unsplashData?.urls!.raw! ?? "https://images.unsplash.com/photo-1626643590239-4d5051bafbcc?ixid=MnwxOTUzMTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2MjY5Njc0MjI&ixlib=rb-1.2.1")!)
.aspectRatio(contentMode: .fit)
}
.task {
await getURL()
}
}
func getURL() async {
do {
let (data, _) = try await URLSession.shared.data(from: requestURL!)
unsplashData = try JSONDecoder().decode(UnsplashData.self, from: data)
} catch {
print("Failed to fetch image")
}
}
}
import Foundation
// MARK: - UnsplashData
struct UnsplashData: Codable {
let id: String?
let createdAt, updatedAt, promotedAt: Date?
let width, height: Int?
let color, blurHash: String?
let unsplashDataDescription: String?
let altDescription: String?
let urls: Urls?
let links: UnsplashDataLinks?
let categories: [String]?
let likes: Int?
let likedByUser: Bool?
let currentUserCollections: [String]?
let sponsorship: JSONNull?
let user: User?
let exif: Exif?
let location: Location?
let views, downloads: Int?
enum CodingKeys: String, CodingKey {
case id
case createdAt = "created_at"
case updatedAt = "updated_at"
case promotedAt = "promoted_at"
case width, height, color
case blurHash = "blur_hash"
case unsplashDataDescription = "description"
case altDescription = "alt_description"
case urls, links, categories, likes
case likedByUser = "liked_by_user"
case currentUserCollections = "current_user_collections"
case sponsorship, user, exif, location, views, downloads
}
}
// MARK: - Exif
struct Exif: Codable {
let make, model, exposureTime, aperture: String?
let focalLength: String?
let iso: Int?
enum CodingKeys: String, CodingKey {
case make, model
case exposureTime = "exposure_time"
case aperture
case focalLength = "focal_length"
case iso
}
}
// MARK: - UnsplashDataLinks
struct UnsplashDataLinks: Codable {
let linksSelf, html, download, downloadLocation: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, download
case downloadLocation = "download_location"
}
}
// MARK: - Location
struct Location: Codable {
let title, name, city, country: String?
let position: Position?
}
// MARK: - Position
struct Position: Codable {
let latitude, longitude: Double?
}
// MARK: - Urls
struct Urls: Codable {
let raw, full, regular, small: String?
let thumb: String?
}
// MARK: - User
struct User: Codable {
let id: String?
let updatedAt: Date?
let username, name, firstName, lastName: String?
let twitterUsername: String?
let portfolioURL: String?
let bio: String?
let location: String?
let links: UserLinks?
let profileImage: ProfileImage?
let instagramUsername: String?
let totalCollections, totalLikes, totalPhotos: Int?
let acceptedTos: Bool?
enum CodingKeys: String, CodingKey {
case id
case updatedAt = "updated_at"
case username, name
case firstName = "first_name"
case lastName = "last_name"
case twitterUsername = "twitter_username"
case portfolioURL = "portfolio_url"
case bio, location, links
case profileImage = "profile_image"
case instagramUsername = "instagram_username"
case totalCollections = "total_collections"
case totalLikes = "total_likes"
case totalPhotos = "total_photos"
case acceptedTos = "accepted_tos"
}
}
// MARK: - UserLinks
struct UserLinks: Codable {
let linksSelf, html, photos, likes: String?
let portfolio, following, followers: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, photos, likes, portfolio, following, followers
}
}
// MARK: - ProfileImage
struct ProfileImage: Codable {
let small, medium, large: String?
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
在您的模型中,UnsplashData 和 User 替换 Date?用字符串?
之后,这是我测试答案的方式:
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var unsplashData: UnsplashData?
var body: some View {
VStack {
if let unsplash = unsplashData {
Text("user is \(unsplash.user?.name ?? "no name")")
} else {
Text("testing testing")
}
}
.task {
await getUnsplashData()
}
}
func getUnsplashData() async {
let fetchResponse: UnsplashData? = await fetchIt()
if let theResponse = fetchResponse {
self.unsplashData = theResponse
print("\n-----> getUnsplashData: \(theResponse)")
}
}
func fetchIt<T: Decodable>() async -> T? {
let url = URL(string: "https://api.unsplash.com/photos/random?client_id=TSozaArCYtCWcXnnUkh4KvKJ5ZfmVOn_FYbIVVn76Ew")!
let request = URLRequest(url: url)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
// throw URLError(.badServerResponse) // todo
print(URLError(.badServerResponse))
return nil
}
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
catch {
return nil
}
}
}
我的 swift 包 UnsplashSwiftUI 遇到了一些问题 在 WWDC 之前,我遇到了一些问题导致我的视图重新加载(正如您在主分支上看到的那样)但是当 async/await 宣布时,这似乎是我的包的绝佳机会。
我正在开发分支上 async/await 开发包。
但是,我现在在处理异步 API 请求时遇到了一些问题。
这是我的最小可重现示例,我从我的异步函数 getURL() 的 catch 块中得到打印错误 'Failed to fetch image'。我还尝试在内部使用异步调用任务。
//From this
.task {
await getURL()
}
//To this
.task {
async {
await getURL()
}
}
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack {
UnsplashRandom(clientId: "TSozaArCYtCWcXnnUkh4KvKJ5ZfmVOn_FYbIVVn76Ew")
.frame(width: 500, height: 500)
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
import SwiftUI
@available(iOS 15, OSX 12, *)
public struct UnsplashRandom: View {
//MARK: Parameters
//Required parameters
var clientId: String //Unsplash API access key
@State private var unsplashData: UnsplashData? = nil
@State private var requestURL: URL? = nil
//MARK: Init
public init(clientId: String) {
self.clientId = clientId
let url = URL(string: "https://api.unsplash.com/")!
guard var components = URLComponents(url: url.appendingPathComponent("photos/random"), resolvingAgainstBaseURL: true)
else { fatalError("Couldn't append path component")}
components.queryItems = [URLQueryItem(name: "client_id", value: clientId)]
_requestURL = State(initialValue: components.url!)
}
//MARK: Body
public var body: some View {
//MARK: Main View
ZStack(alignment: .bottomTrailing) {
//MARK: Remote Image
AsyncImage (url: URL(string: unsplashData?.urls!.raw! ?? "https://images.unsplash.com/photo-1626643590239-4d5051bafbcc?ixid=MnwxOTUzMTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2MjY5Njc0MjI&ixlib=rb-1.2.1")!)
.aspectRatio(contentMode: .fit)
}
.task {
await getURL()
}
}
func getURL() async {
do {
let (data, _) = try await URLSession.shared.data(from: requestURL!)
unsplashData = try JSONDecoder().decode(UnsplashData.self, from: data)
} catch {
print("Failed to fetch image")
}
}
}
import Foundation
// MARK: - UnsplashData
struct UnsplashData: Codable {
let id: String?
let createdAt, updatedAt, promotedAt: Date?
let width, height: Int?
let color, blurHash: String?
let unsplashDataDescription: String?
let altDescription: String?
let urls: Urls?
let links: UnsplashDataLinks?
let categories: [String]?
let likes: Int?
let likedByUser: Bool?
let currentUserCollections: [String]?
let sponsorship: JSONNull?
let user: User?
let exif: Exif?
let location: Location?
let views, downloads: Int?
enum CodingKeys: String, CodingKey {
case id
case createdAt = "created_at"
case updatedAt = "updated_at"
case promotedAt = "promoted_at"
case width, height, color
case blurHash = "blur_hash"
case unsplashDataDescription = "description"
case altDescription = "alt_description"
case urls, links, categories, likes
case likedByUser = "liked_by_user"
case currentUserCollections = "current_user_collections"
case sponsorship, user, exif, location, views, downloads
}
}
// MARK: - Exif
struct Exif: Codable {
let make, model, exposureTime, aperture: String?
let focalLength: String?
let iso: Int?
enum CodingKeys: String, CodingKey {
case make, model
case exposureTime = "exposure_time"
case aperture
case focalLength = "focal_length"
case iso
}
}
// MARK: - UnsplashDataLinks
struct UnsplashDataLinks: Codable {
let linksSelf, html, download, downloadLocation: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, download
case downloadLocation = "download_location"
}
}
// MARK: - Location
struct Location: Codable {
let title, name, city, country: String?
let position: Position?
}
// MARK: - Position
struct Position: Codable {
let latitude, longitude: Double?
}
// MARK: - Urls
struct Urls: Codable {
let raw, full, regular, small: String?
let thumb: String?
}
// MARK: - User
struct User: Codable {
let id: String?
let updatedAt: Date?
let username, name, firstName, lastName: String?
let twitterUsername: String?
let portfolioURL: String?
let bio: String?
let location: String?
let links: UserLinks?
let profileImage: ProfileImage?
let instagramUsername: String?
let totalCollections, totalLikes, totalPhotos: Int?
let acceptedTos: Bool?
enum CodingKeys: String, CodingKey {
case id
case updatedAt = "updated_at"
case username, name
case firstName = "first_name"
case lastName = "last_name"
case twitterUsername = "twitter_username"
case portfolioURL = "portfolio_url"
case bio, location, links
case profileImage = "profile_image"
case instagramUsername = "instagram_username"
case totalCollections = "total_collections"
case totalLikes = "total_likes"
case totalPhotos = "total_photos"
case acceptedTos = "accepted_tos"
}
}
// MARK: - UserLinks
struct UserLinks: Codable {
let linksSelf, html, photos, likes: String?
let portfolio, following, followers: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, photos, likes, portfolio, following, followers
}
}
// MARK: - ProfileImage
struct ProfileImage: Codable {
let small, medium, large: String?
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
在您的模型中,UnsplashData 和 User 替换 Date?用字符串?
之后,这是我测试答案的方式:
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var unsplashData: UnsplashData?
var body: some View {
VStack {
if let unsplash = unsplashData {
Text("user is \(unsplash.user?.name ?? "no name")")
} else {
Text("testing testing")
}
}
.task {
await getUnsplashData()
}
}
func getUnsplashData() async {
let fetchResponse: UnsplashData? = await fetchIt()
if let theResponse = fetchResponse {
self.unsplashData = theResponse
print("\n-----> getUnsplashData: \(theResponse)")
}
}
func fetchIt<T: Decodable>() async -> T? {
let url = URL(string: "https://api.unsplash.com/photos/random?client_id=TSozaArCYtCWcXnnUkh4KvKJ5ZfmVOn_FYbIVVn76Ew")!
let request = URLRequest(url: url)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
// throw URLError(.badServerResponse) // todo
print(URLError(.badServerResponse))
return nil
}
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
catch {
return nil
}
}
}