-
-
Save lcs-rgordon/01cff5a68d75c58fd3ef7ad216b3a252 to your computer and use it in GitHub Desktop.
A class written to provide data for my implementation of the Friends app, for the Days 60-61 challenge in #100DaysOfSwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DataProvider.swift | |
// Friends | |
// | |
// Created by Russell Gordon on 2020-12-26. | |
// | |
import CoreData | |
import Foundation | |
import SwiftUI | |
// BASED ON: https://developer.apple.com/documentation/coredata/loading_and_displaying_a_large_data_feed | |
class DataProvider { | |
init() { | |
// Count records on a background thread | |
let backgroundManagedObjectContext = PersistenceController.shared.container.newBackgroundContext() | |
backgroundManagedObjectContext.performAndWait { | |
// Access User entity | |
let fetchRequest : NSFetchRequest<User> = User.fetchRequest() | |
do { | |
let itemCount = try backgroundManagedObjectContext.count(for: fetchRequest) | |
if itemCount == 0 { | |
// No user data exists, so go get it | |
print("No user data exists, going out to get it...") | |
fetchAndSaveUserData() | |
} else { | |
print("User data already exists.") | |
} | |
} | |
catch let error as NSError { | |
print("Error counting users: \(error.localizedDescription)") | |
} | |
} | |
} | |
/// Get the basic user data from Hacking with Swift site | |
func fetchAndSaveUserData() { | |
// 1. Prepare a URLRequest to get user data | |
let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") | |
var request = URLRequest(url: url!) | |
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
request.httpMethod = "GET" | |
// 2. Run the request and process the response | |
URLSession.shared.dataTask(with: request) { data, response, error in | |
// handle the result here – attempt to unwrap optional data provided by task | |
guard let unwrappedData = data else { | |
// Show the error message | |
print("No data in response: \(error?.localizedDescription ?? "Unknown error")") | |
return | |
} | |
// DEBUG: Inspect contents of JSON file retrieved from the server | |
print(String(data: unwrappedData, encoding: .utf8)!) | |
// Now decode from JSON directly into Core Data managed objects | |
// Do this on a background thread to avoid concurrency issues | |
// SEE: https://stackoverflow.com/questions/49454965/how-to-save-to-managed-object-context-in-a-background-thread-in-core-data | |
// SEE: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Concurrency.html | |
// SEE: https://www.donnywals.com/using-codable-with-core-data-and-nsmanagedobject/ | |
let backgroundManagedObjectContext = PersistenceController.shared.container.newBackgroundContext() | |
// Set unused undoManager to nil for macOS (it is nil by default on iOS) | |
// to reduce resource requirements. | |
backgroundManagedObjectContext.undoManager = nil | |
// Set the merge policy for duplicate entries – ensure that duplicate entries get merged into a single unique record | |
// "To help resolve this, Core Data gives us constraints: we can make one attribute constrained so that it must always be unique. We can then go ahead and make as many objects as we want, unique or otherwise, but as soon as we ask Core Data to save those objects it will resolve duplicates so that only one piece of data gets written." | |
backgroundManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | |
// Decode on a background thread | |
backgroundManagedObjectContext.performAndWait { | |
// Decode into Core Data objects | |
let decoder = JSONDecoder(context: backgroundManagedObjectContext) | |
if let decodedData = try? decoder.decode([User].self, from: unwrappedData) { | |
print("There were \(decodedData.count) users placed into Core Data") | |
} else { | |
print("Could not decode from JSON into Core Data") | |
} | |
// Required to actually write the changes to Core Data | |
do { | |
// Save Core Data objects | |
try backgroundManagedObjectContext.save() | |
// // DEBUG: Count records | |
// let fetchRequest : NSFetchRequest<User> = User.fetchRequest() | |
// do { | |
// let itemCount = try backgroundManagedObjectContext.count(for: fetchRequest) | |
// print("There are \(itemCount) users now.") | |
// } | |
// catch let error as NSError { | |
// print("Error counting users: \(error.localizedDescription)") | |
// } | |
// Now actually try to load the avatars | |
self.fetchAndSaveAvatars() | |
} catch { | |
fatalError("Failure to save context: \(error)") | |
} | |
} | |
}.resume() | |
} | |
/// Get avatars for all the users just loaded | |
func fetchAndSaveAvatars() { | |
// We know there should be exactly 100 users, so... | |
for _ in 1...100 { | |
// 1. Prepare a URL to fetch the image from | |
let someNumber = Int.random(in: 0...99) | |
let avatarAddress = "https://randomuser.me/api/portraits/women/\(someNumber).jpg" | |
let url = URL(string: avatarAddress)! | |
// 2. Run the request and process the response | |
URLSession.shared.dataTask(with: url) { data, response, error in | |
// DEBUG: Did avatar data come back? | |
// print("retrieved avatar data") | |
// Handle the result here – attempt to unwrap optional data provided by task | |
guard let imageData = data else { | |
// Show the error message | |
print("No data in response: \(error?.localizedDescription ?? "Unknown error")") | |
return | |
} | |
// Attempt to create an instance of UIImage using the data from the server | |
if let loadedImage = UIImage(data: imageData) { | |
// Set up a managed object context to run on a background thread | |
let backgroundManagedObjectContext = PersistenceController.shared.container.newBackgroundContext() | |
backgroundManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | |
backgroundManagedObjectContext.undoManager = nil | |
// Run this block on a background thread | |
backgroundManagedObjectContext.performAndWait { | |
// Build a fetch request that will get the next single user without an avatar image | |
let fetchRequest: NSFetchRequest<User> = User.fetchRequest() | |
fetchRequest.predicate = NSPredicate(format: "avatar == nil") | |
fetchRequest.sortDescriptors = [ | |
NSSortDescriptor(keyPath: \User.name, ascending: true) | |
] | |
fetchRequest.fetchLimit = 1 | |
// Get the user | |
var results: [User] = [] | |
do { | |
results = try backgroundManagedObjectContext.fetch(fetchRequest) | |
} | |
catch let error as NSError { | |
print("Error getting results from Users entity: \(error.localizedDescription)") | |
} | |
// Get a reference to the user returned | |
// There should only be one object in the result set | |
if let user = results.first { | |
// Save data to object encoded as a JPEG | |
user.avatar = loadedImage.jpegData(compressionQuality: 1.0) | |
// DEBUG | |
// print("successfully retrieved and saved avatar image to core data") | |
// Required to actually write the changes to Core Data | |
do { | |
// Save Core Data objects | |
try backgroundManagedObjectContext.save() | |
} catch { | |
fatalError("Failure to save context: \(error)") | |
} | |
} | |
} | |
} else { | |
print("Couldn't load avatar image from server.") | |
} | |
}.resume() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment