#ios #json #swift
Вопрос:
Я наткнулся на ошибку декодирования json в примере в книге. Ошибка гласит:
2021-05-06 07:01:31.193094 1000 Photorama[1562:29734] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Error fetching interesting photos: keyNotFound(CodingKeys(stringValue: "photos", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "photos", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "photos", intValue: nil) ("photos").", underlyingError: nil))
Однако в данных json, возвращенных с Flickr, ключ «фотографии» действительно существовал. Сокращенная копия данных json выглядит следующим образом:
{
"extra": {
"explore_date": "2021-05-04",
"next_prelude_interval": 57778
},
"photos": {
"page": 1,
"pages": 5,
"perpage": 100,
"total": 500,
"photo": [
{
"id": "51156301899",
"owner": "138752302@N05",
"secret": "31d327f54f",
"server": "65535",
"farm": 66,
"title": "*the power of the sun*",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0,
"datetaken": "2021-04-20 06:52:11",
"datetakengranularity": "0",
"datetakenunknown": "0",
"url_z": "https://live.staticflickr.com/65535/51156301899_31d327f54f_z.jpg",
"height_z": 380,
"width_z": 640
},
"stat": "ok"
}
Код программы выглядит следующим образом:
AppDelegate.swift (без изменений)
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
Сцена удалена.свифт
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
let rootViewController = window!.rootViewController as! UINavigationController
let photosViewController = rootViewController.topViewController as! PhotosViewController
photosViewController.store = PhotoStore()
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
PhotosViewController.swift
import UIKit
class PhotosViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
var store: PhotoStore!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// store.fetchInterestingPhotos()
store.fetchInterestingPhotos {
(photoResult) in
switch photoResult {
case let .success(photos):
print("Successfully found (photos.count) photos")
case let .failure(error):
print("Error fetching interesting photos: (error)")
}
}
}
}
FlickrAPI.swift
import Foundation
enum EndPoint: String {
case interestingPhotos = "flickr.interestingness.getList"
}
/*
https://api.flickr.com/services/rest/?method=flickr.interestingness.getList
amp;api_key=a6d819499131071f158fd740860a5a88amp;extras=url_z,date_taken
amp;format=jsonamp;nojsoncallback=1
*/
struct FlickrAPI {
private static let baseURLString = "https://api.flickr.com/services/rest"
private static let apiKey = "a6d819499131071f158fd740860a5a88"
// assemble url from baseURLString and query items
private static func flickrURL (endPoint: EndPoint, parameters: [String: String]?) -> URL {
var components = URLComponents(string: baseURLString)!
var queryItems = [URLQueryItem]()
let baseParams = [
"method": endPoint.rawValue,
"format": "json",
"nojsoncallback": "1",
"api_key": apiKey
]
for (key, value) in baseParams {
let item = URLQueryItem(name: key, value: value)
queryItems.append(item)
}
if let addtionalParams = parameters {
for (key, value) in addtionalParams {
let item = URLQueryItem(name: key, value: value)
queryItems.append(item)
}
}
components.queryItems = queryItems
return components.url!
}
static var interestingPhotoURL: URL {
// url_z is a URL shortener (zipper) for convenience and beauty
return flickrURL(endPoint: .interestingPhotos,
parameters: ["extras": "url_z,date_taken"])
}
static func photos (fromJSON data: Data) -> Result<[Photo], Error> {
do {
let decoder = JSONDecoder()
let flickrResponse = try decoder.decode(FlickrResponse.self, from: data)
return .success(flickrResponse.photosInfo.photos)
} catch {
return .failure(error)
}
}
}
struct FlickrResponse: Codable {
//let photos: FlickrPhotosResponse
let photosInfo: FlickrPhotosResponse
enum CodingKeys: String, CodingKey {
case photosInfo = "photos"
}
}
struct FlickrPhotosResponse: Codable {
//let photo: [Photo]
let photos: [Photo]
enum CodingKyes: String, CodingKey {
case photos = "photo"
}
}
PhotoStore.swift
import Foundation
class PhotoStore {
private let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()
private func processPhotosRequest (data: Data?, error: Error?) ->
Result<[Photo], Error> {
guard let jsonData = data else {
return .failure(error!)
}
return FlickrAPI.photos(fromJSON: jsonData)
}
func fetchInterestingPhotos (completion: @escaping (Result<[Photo], Error>) -> Void) {
let url = FlickrAPI.interestingPhotoURL
let request = URLRequest(url: url)
let task = session.dataTask(with: request) {
(data, response, error) in
/*if let jsonData = data {
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
} else if let requestError = error {
print("Error fetching interest photos: (requestError)")
} else {
print("Unexpect error with the request")
}*/
let result = self.processPhotosRequest(data: data, error: error)
completion(result)
}
task.resume()
}
}
Фото.swift
import Foundation
class Photo: Codable {
let title: String
let remoteURL: URL
let photoID: String
let dateTaken: Date
public init (title: String, remoteURL: URL, photoID: String, dateTaken: Date) {
self.title = title
self.remoteURL = remoteURL
self.photoID = photoID
self.dateTaken = dateTaken
}
enum CodingKeys: String, CodingKey {
case title
case remoteURL = "url_z"
case photoID = "id"
case dateTaken = "datetaken"
}
}
Я хотел бы получить совет, в чем проблема и как ее решить. Большое вам спасибо за вашу помощь!
Комментарии:
1. Попробуйте вставить свой ответ API в app.quicktype. ввод и использование сгенерированного кода
2. Вы предоставляете неверный формат JSON в своем вопросе. Кроме того, пожалуйста, предоставьте только код, необходимый для проверки другими. Нет необходимости включать appdelegate, scenedelegate и т. Д., Если это не нужно
3. Вам нужно научиться читать и интерпретировать сообщения об ошибках, в большинстве случаев это не так сложно, и это необходимые знания для разработчика программного обеспечения
Ответ №1:
Файл photos
в json не является массивом. Это предмет.
Вставьте свой json в https://app.quicktype.io для того, чтобы получить правильные кодируемые объекты.
// MARK: - FlickrPhotosResponse
struct FlickrPhotosResponse: Codable {
let extra: Extra
let photos: Photos
}
// MARK: - Extra
struct Extra: Codable {
let exploreDate: String
let nextPreludeInterval: Int
enum CodingKeys: String, CodingKey {
case exploreDate = "explore_date"
case nextPreludeInterval = "next_prelude_interval"
}
}
// MARK: - Photos
struct Photos: Codable {
let page, pages, perpage, total: Int
let photo: [Photo]
let stat: String
}
// MARK: - Photo
struct Photo: Codable {
let id, owner, secret, server: String
let farm: Int
let title: String
let ispublic, isfriend, isfamily: Int
let datetaken, datetakengranularity, datetakenunknown: String
let urlZ: String
let heightZ, widthZ: Int
enum CodingKeys: String, CodingKey {
case id, owner, secret, server, farm, title, ispublic, isfriend, isfamily, datetaken, datetakengranularity, datetakenunknown
case urlZ = "url_z"
case heightZ = "height_z"
case widthZ = "width_z"
}
}