With the persistent container set up, you can now interact with Core Data. Primarily, you will do this through its viewContext. This is how you will both create new entities and save changes.
The viewContext is an instance of NSManagedObjectContext. This is the portal through which you interact with your entities. You can think of the managed object context as an intelligent scratch pad. When you ask the context to fetch some entities, the context will work with its persistent store coordinator to bring temporary copies of the entities and object graph into memory. Unless you ask the context to save its changes, the persisted data remains the same.
When an entity is created, it should be inserted into a managed object context.
Open FlickrAPI.swift and import CoreData.
import Foundation import CoreData
Next, update the photo(fromJSON:) method to take in an additional argument of type NSManagedObjectContext and use this context to insert new Photo instances.
private static func photo(fromJSON json: [String : Any], into context: NSManagedObjectContext) -> Photo? { guard let photoID = json["id"] as? String, let title = json["title"] as? String, let dateString = json["datetaken"] as? String, let photoURLString = json["url_h"] as? String, let url = URL(string: photoURLString), let dateTaken = dateFormatter.date(from: dateString) else { // Don't have enough information to construct a Photo return nil }return Photo(title: title, photoID: photoID, remoteURL: url, dateTaken: dateTaken)var photo: Photo! context.performAndWait { photo = Photo(context: context) photo.title = title photo.photoID = photoID photo.remoteURL = url as NSURL photo.dateTaken = dateTaken as NSDate } return photo }
Each NSManagedObjectContext is associated with a specific concurrency queue, and the viewContext is associated with the main, or UI, queue. You have to interact with a context on the queue that it is associated with. NSManagedObjectContext has two methods that ensure this happens: perform(_:) and performAndWait(_:). The difference between them is that perform(_:) is asynchronous and performAndWait(_:) is synchronous. Because you are returning the result of the insert operation from the photo(fromJSON:into:) method, you use the synchronous method.
The photo(fromJSON:into:) method is called from the method photos(fromJSON:). Update this method to take in a context and pass it to the photo(fromJSON:into:) method.
static func photos(fromJSON data: Data, into context: NSManagedObjectContext) -> PhotosResult { do { ... var finalPhotos = [Photo]() for photoJSON in photosArray { if let photo = photo(fromJSON: photoJSON, into: context) { finalPhotos.append(photo) } }
Finally, you need to pass the viewContext to the FlickrAPI struct once the web service request successfully completes.
Open PhotoStore.swift and update processPhotosRequest(data:error:).
private func processPhotosRequest(data: Data?, error: Error?) -> PhotosResult { guard let jsonData = data else { return .failure(error!) } return FlickrAPI.photos(fromJSON: jsonData, into: persistentContainer.viewContext) }
Build and run the application now that all errors have been addressed. Although the behavior remains unchanged, the application is now backed by Core Data. In the next section, you will implement saving for both the photos and their associated image data.
Recall that NSManagedObject changes do not persist until you tell the context to save these changes.
Open PhotoStore.swift and update fetchInterestingPhotos(completion:) to save the changes to the context after Photo entities have been inserted into the context.
func fetchInterestingPhotos(completion: @escaping (PhotosResult) -> Void) { let url = FlickrAPI.interestingPhotosURL let request = URLRequest(url: url) let task = session.dataTask(with: request) { (data, response, error) -> Void inletvar result = self.processPhotosRequest(data: data, error: error) if case .success = result { do { try self.persistentContainer.viewContext.save() } catch let error { result = .failure(error) } } OperationQueue.main.addOperation { completion(result) } } task.resume() }