【问题标题】:Core Data model attribute types not compatible with program typesCore Data 模型属性类型与程序类型不兼容
【发布时间】:2021-08-11 02:34:17
【问题描述】:

我正在学习 iOS 编程书中的示例。书中的示例从 Flickr 下载照片并将它们放置在集合视图中。每张照片都通过链接作为 json 代码中对象的元素之一下载。该示例使用 Core Data 进行数据持久化,并涉及从 Core Data 托管对象类型到自定义对象类型的转换。

编译器报告“无法将 '[Photo]' 类型的值转换为预期的参数类型 '[FlickrPhoto]'”。 [Photo] 是由 Core Data 根据我提供的 Entity 和 Attributes 信息自动生成的 Photo 对象数组,而 [FlickrPhoto] 是 Fl​​ickrPhoto 对象数组,它们是自定义对象。

请让我知道存在什么问题并提出一些解决方案。谢谢!

相关代码如下:

照片+CoreDataClass.swift

import Foundation
import CoreData

@objc(Photo)
public class Photo: NSManagedObject {

}

照片+CoreDataProperties.swift

import Foundation
import CoreData


extension Photo {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
        return NSFetchRequest<Photo>(entityName: "Photo")
    }

    @NSManaged public var datetaken: String?
    @NSManaged public var datetakengranularity: String?
    @NSManaged public var datetakenunknow: String?
    @NSManaged public var farm: Int64
    @NSManaged public var heightZ: Int64
    @NSManaged public var id: String?
    @NSManaged public var isfamily: Int64
    @NSManaged public var isfriend: Int64
    @NSManaged public var ispublic: Int64
    @NSManaged public var owner: String?
    @NSManaged public var secret: String?
    @NSManaged public var server: String?
    @NSManaged public var title: String?
    @NSManaged public var urlZ: String?
    @NSManaged public var widthZ: Int64

}

extension Photo : Identifiable {

}

照片.swift

import Foundation

struct FlickrPhoto: Codable {
    let id, owner, secret, server: String
    let farm: Int
    let title: String
    let ispublic, isfriend, isfamily: Int
    let datetaken: String
    let datetakengranularity: Datetakengranularity
    let 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"
    }
}
enum Datetakengranularity: Codable {
    case integer(Int)
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Int.self) {
            self = .integer(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Datetakengranularity.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Datetakengranularity"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .integer(let x):
            try container.encode(x)
        case .string(let x):
            try container.encode(x)
        }
    }
}

extension FlickrPhoto: Equatable {
    static func == (lhs: FlickrPhoto, rhs: FlickrPhoto) -> Bool {
        // Two Photos are the same if they have the same photoID
        return lhs.id == rhs.id
    }
}

PhotoStore.swift

import UIKit
import CoreData

class PhotoStore {
    private let session: URLSession = {
        let config = URLSessionConfiguration.default
        return URLSession(configuration: config)
    }()
    let imageStore = ImageStore()
    let persistenContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Photorama")
        container.loadPersistentStores { (description, error) in
            if let error = error {
                print("Error setting up Core Data (\(error))")
            }
        }
        return container
    }()
    private func processPhotosRequest (data: Data?, error: Error?) ->
    Result<[FlickrPhoto], Error> {
        guard let jsonData = data else {
            return .failure(error!)
        }
        //return FlickrAPI.photos(fromJSON: jsonData)
        let context = persistenContainer.viewContext
        
        switch FlickrAPI.photos(fromJSON: jsonData) {
        case let .success(flickrPhotos):
            let photos = flickrPhotos.map { flickrPhoto -> Photo in
                var photo: Photo!
                context.performAndWait {
                    do {
                        photo = Photo(context: context)
                        photo.id = flickrPhoto.id
                        photo.owner = flickrPhoto.owner
                        photo.secret = flickrPhoto.secret
                        photo.server = flickrPhoto.server
                        photo.farm = Int64(flickrPhoto.farm)
                        photo.title = flickrPhoto.title
                        photo.ispublic = Int64(flickrPhoto.ispublic)
                        photo.isfriend = Int64(flickrPhoto.isfriend)
                        photo.isfamily = Int64(flickrPhoto.isfamily)
                        photo.datetaken = flickrPhoto.datetaken
                        photo.datetakengranularity = try String(from: flickrPhoto.datetakengranularity as! Decoder)
                        photo.datetakenunknow = flickrPhoto.datetakenunknown
                        photo.urlZ = flickrPhoto.urlZ
                        photo.heightZ = Int64(flickrPhoto.heightZ!)
                        photo.widthZ = Int64(flickrPhoto.widthZ!)
                    } catch {
                        print("error encountered")
                    }
                }
                return photo
            }
            return .success(photos) // Compiler reports the above mentioned error here
        case let .failure(error):
            return .failure(error)
        }
    }
    
    func fetchInterestingPhotos (completion: @escaping (Result<[FlickrPhoto], Error>) -> Void) {
        let url = FlickrAPI.interestingPhotoURL
        let request = URLRequest(url: url)
        let task = session.dataTask(with: request) {
            (data, response, error) in
            
            let result = self.processPhotosRequest(data: data, error: error)
            OperationQueue.main.addOperation {
                completion(result)
            }
        }
        task.resume()
    }
    func fetchImage (for photo: FlickrPhoto, completion: @escaping (Result<UIImage, Error>) -> Void) {
        
        let photoKey = photo.id
        if let image = imageStore.image(forKey: photoKey) {
            OperationQueue.main.addOperation {
                completion(.success(image))
            }
            return
        }
        guard let photoURL = photo.urlZ else { return }
        guard let requestURL = URL(string: photoURL)  else {
            completion(.failure(PhotoError.missingImageURL))
            return
        }
        let request = URLRequest(url: requestURL)
        let task = session.dataTask(with: request) {
            (data, response, error) in
            let result = self.processImageRequest(data: data, error: error)
            if case let .success(image) = result {
                self.imageStore.setImage(image, forKey: photoKey)
            }
            OperationQueue.main.addOperation {
                completion(result)
            }
        }
        task.resume()
    }
    private func processImageRequest (data: Data?, error: Error?) -> Result<UIImage, Error> {
        guard let imageData = data,
              let image = UIImage(data: imageData) else {
            // Couldn't create an image
            if data == nil {
                return .failure(error!)
            } else {
                return .failure(PhotoError.imageCreationError)
            }
        }
        return .success(image)
    }
}

enum PhotoError: Error {
    case imageCreationError
    case missingImageURL
}

最后是 xcdatamodeld 设置的快照:

将根据需要提供更多代码。

【问题讨论】:

  • 感谢@JoakimDanielson 的回复。我的问题正如标题所暗示的那样,照片是由核心数据模型自动生成的照片类型,程序通过根据 FlickrPhoto 结构的属性添加实体及其属性来配置该模型。但是,当 'processPhotosRequest' 方法中的 'return .success(photos)' 语句中的代码打算使用 '[Photo]' 类型的照片作为 '.success()' 的参数时,然而,应该是“[FlickrPhoto]”类型,编译器报告给出的投诉如我的问题所示。

标签: ios swift core-data


【解决方案1】:

在函数processPhotosRequest 中有一个从 FlickrPhoto 到 Photo 对象的映射,这样做是为了数据可以存储在 Core Data 中,但 不是 那些应该返回的对象,所以该行

return .success(photos) 

应该改为

return .success(flickrPhotos) 

修复编译错误并返回正确的东西。

此外,由于您不需要保留 Photo 对象数组,因此可以对对象的映射进行一些更改

case let .success(flickrPhotos):
    flickrPhotos.forEach { flickrPhoto -> Photo in
        context.performAndWait {
            do {
                let photo = Photo(context: context)
                //rest of mapping is the same
                
                context.save()
            } catch {
                print(error)
            }
        }
    }

在这里我还添加了一个 save() 调用,以便将您的数据保存到 Core Data 存储中,因为我在您的代码的其他地方找不到这样的调用

【讨论】:

  • 非常感谢@Joakim Danielson。您提供的解决方案解决了我要问的问题。但另一个问题仍然存在。 context.save() 方法调用在程序的另一个源文件中,这里我没有展示。
  • 然后你可以删除我的存档,如果你愿意,也可以删除整个 do/catch
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 1970-01-01
  • 2018-08-07
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 2021-07-05
相关资源
最近更新 更多