Is there a way to assign a variable of Swift enum type to a variable of NSObject type?

485 Views Asked by At

In my program code which is posted below, I need to assign a variable of Swift enum type to a variable of NSObject type. However, the compiler doesn't allow this. I know this is not allowed. So, I wonder whether there is a way to change the enum somehow so that it can be assigned to an NSObject variable. Thank you!

Photo+CoreDataClass.swift

import Foundation
import CoreData

@objc(Photo)
public class Photo: NSManagedObject {

}

Photo+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: NSObject?
    @NSManaged public var datetakenunknow: String?
    @NSManaged public var farm: Int32
    @NSManaged public var heightZ: Int32
    @NSManaged public var photoID: String?
    @NSManaged public var isfamily: Int32
    @NSManaged public var isfriend: Int32
    @NSManaged public var ispublic: Int32
    @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: Int32

}

extension Photo : Identifiable {

}

FlickrPhoto.swift

import Foundation

// MARK: - Photo
struct FlickrPhoto: Codable {
    let photoID, 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 owner, secret, server, farm, title, ispublic, isfriend, isfamily, datetaken, datetakengranularity, datetakenunknown
        case photoID = "id"
        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.photoID == rhs.photoID
    }
}

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 _ = flickrPhotos.map { flickrPhoto -> Photo in
                let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
                let predicate = NSPredicate(format: "\(#keyPath(Photo.photoID)) == \(flickrPhoto.photoID)")
                fetchRequest.predicate = predicate
                var photo: Photo!
                context.performAndWait {
                        photo = Photo(context: context)
                        photo.photoID = flickrPhoto.photoID
                        photo.owner = flickrPhoto.owner
                        photo.secret = flickrPhoto.secret
                        photo.server = flickrPhoto.server
                        photo.farm = Int32(flickrPhoto.farm)
                        photo.title = flickrPhoto.title
                        photo.ispublic = Int32(flickrPhoto.ispublic)
                        photo.isfriend = Int32(flickrPhoto.isfriend)
                        photo.isfamily = Int32(flickrPhoto.isfamily)
                        photo.datetaken = flickrPhoto.datetaken
                        photo.datetakengranularity = flickrPhoto.datetakengranularity // The compiler reports error here: 
                             // Cannot assign value of type 'Datetakengranularity' to type 'NSObject?'
                        photo.datetakenunknow = flickrPhoto.datetakenunknown
                        photo.urlZ = flickrPhoto.urlZ
                        photo.heightZ = Int32(flickrPhoto.heightZ!)
                        photo.widthZ = Int32(flickrPhoto.widthZ!)
                }
                return photo
            }
            return .success(flickrPhotos)
        case let .failure(error):
            return .failure(error)
        }
    }
    func fetchAllPhotos (completion: @escaping (Result<[Photo], Error>) -> Void) -> Void {
        let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
        let sortByDataTaken = NSSortDescriptor(key: #keyPath(Photo.datetaken), ascending: true)
        fetchRequest.sortDescriptors = [sortByDataTaken]
        let viewContext = persistenContainer.viewContext
        viewContext.perform {
            do {
                let allPhotos = try viewContext.fetch(fetchRequest)
                completion(.success(allPhotos))
            } catch {
                completion(.failure(error))
            }
        }
    }
    func fetchRecentPhotos (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
            
            var result = self.processPhotosRequest(data: data, error: error)
            if case .success = result {
                do {
                    try self.persistenContainer.viewContext.save()
                } catch {
                    result = .failure(error)
                }
            }
            OperationQueue.main.addOperation {
                completion(result)
            }
        }
        task.resume()
    }
    func fetchImage (for photo: Photo, completion: @escaping (Result<UIImage, Error>) -> Void) {
        
        let photoKey = photo.photoID
        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
}

A snapshot of the Photorama.xcdatamodeld interface is shown below:

enter image description here

More code will be provided if necessary. Thanks very much!

2

There are 2 best solutions below

0
Kamil.S On

You could do it using hacky enum byte serialization stored as NSData property in your NSObject.

enum Test {
    case testCase
    case testCase2
}

func bytes<T>(of value: T) -> [UInt8]{
    var value = value
    let size = MemoryLayout<T>.size
    return withUnsafePointer(to: &value, {
        $0.withMemoryRebound(to: UInt8.self,
            capacity: size,
            {
                Array(UnsafeBufferPointer(start: $0, count: size))
            })
        })
}

let testCase: Test = .testCase
let testCase2: Test = .testCase2

var testBytes = bytes(of: testCase)
var testBytes2 = bytes(of: testCase2)

let data = NSData(bytes: &testBytes, length: testBytes.count)
let data2 = NSData(bytes: &testBytes2, length: testBytes2.count)

let testCaseDeserialized = data.bytes.bindMemory(to: Test.self, capacity: 1).pointee
let testCase2Deserialized = data2.bytes.bindMemory(to: Test.self, capacity: 1).pointee
1
Michael May On

I tried bridging the flickrPhoto.datatakengranularity variable to NSObject via AnyObject like this:

photo.datetakengranularity = (flickrPhoto.datetakengranularity as AnyObject as! NSObject) 

And the compiler made no complaints. This may be because in essence AnyObject is equivalent to NSObject and flickrPhoto.datetakengranularity is convertible to AnyObject. I don't know what happened underneath but it worked anyway. I don't know why.

Thanks to all for your concern and support!