Nesting URLSession.shared.dataTask in Swift 4 - nested

I am trying to fetch data from an api where the JSON returned has URLs to other pieces of information that I need, such as
"value1" : "data",
"value2": {
"url": "https://example.com/stuff",
}
My logic is as follows:
func(completion: #escaping ([Data]) -> ()) {
var classArray = [myClass]()
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
guard let resultArray = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
let myObject = myClass(value1: resultArray["value1"]! as! String)
guard let valueUrl = URL(string: resultArray["value2"]! as! String) else { return }
URLSession.shared.dataTask(with: valueUrl) { (data, _, _) in
myObject.value2 = data
classArray.append(myObject)
}.resume()
} catch let error {
print("Failed to create json with error: ", error.localizedDescription)
}
completion(classArray)
}.resume()
}
}
Is this a valid approach or are there better implementations? Trying to avoid a future Pyramid of Doom situation. I have tried putting the inner URLSession call in a separate private function but still receive an empty classArray in the end.

Related

How does the main thread refresh data, after using NSAsynchronousFetchRequest?

I found two ways to get data asynchronously to avoid the main thread being stuck.How does the main thread refresh data, after using NSAsynchronousFetchRequest?
plan A:
#objc public static func fetchObjects(completed: #escaping (_ results: [DraftNote]?) -> Void) {
let taskContext = NotePersistentContainer.shared.newBackgroundContext()
taskContext.perform {
do {
let fetchRequest = DraftNote.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(DraftNote.lastModifiedDate), ascending: false)]
fetchRequest.predicate = NoteFiltrateType.predicateCurrent(by: #keyPath(DraftNote.userID))
let objects = try taskContext.fetch(fetchRequest)
DispatchQueue.main.async {
completed(objects.compactMap { $0 })
}
} catch {
completed(nil)
print("\(#function) error is: \(error as NSError)")
}
}
}
plan B:
#objc static func retrieveObjects(completed: #escaping (_ results: [DraftNote]?) -> Void) {
let fetchRequest = DraftNote.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(DraftNote.lastModifiedDate), ascending: false)]
fetchRequest.predicate = NoteFiltrateType.predicateCurrent(by: #keyPath(DraftNote.userID))
fetchRequest.fetchLimit = .max
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { fetchResult in
if let values = fetchResult.finalResult {
DispatchQueue.main.async {
completed(values)
}
} else {
completed(nil)
}
}
do {
let taskContext = NotePersistentContainer.shared.newBackgroundContext()
try taskContext.execute(asyncFetchRequest)
} catch {
let err = error as NSError
print("\(#function) error: \(err), \(err.userInfo)")
}
}
Both methods are great for fetching data, they don't block the main thread. But the problem is that I can't manipulate the final result, if I call back the final result using DispatchQueue.main.async{} , the main thread can't refresh the UI. How to solve this problem please?

Bad excess while accessing core data Entity

I am trying to fetch some record from some entity , but when trying to fetch frequently i am getting Bad Access error ,and app is crashing . please help .
var mContext:NSManagedObjectContext! = appDelegate.persistentContainer.viewContext
func getAllRoomName() -> [String] {
let fetchRequest: NSFetchRequest<SwitchMO> = SwitchMO.fetchRequest()
var arrRoomNames = [String]()
do {
if let arrSwitchesMo = try? mContext.fetch(fetchRequest) as? [SwitchMO]
{
for switchMo in arrSwitchesMo ?? []
{
arrRoomNames.append(switchMo.roomName ?? "")
}
}
} catch {
print("Error with request: \(error)")
}
arrRoomNames = Array(Set(arrRoomNames))
return arrRoomNames;
}
Bad Access Error
How can i get rid of this , Please help me .
If you are using a specific fetch request a type cast is redundant. And if you are using do catch don't try?
func getAllRoomName() -> [String] {
let fetchRequest: NSFetchRequest<SwitchMO> = SwitchMO.fetchRequest()
var arrRoomNames = [String]()
do {
let arrSwitchesMo = try mContext.fetch(fetchRequest)
for switchMo in arrSwitchesMo {
arrRoomNames.append(switchMo.roomName ?? "")
}
arrRoomNames = Array(Set(arrRoomNames))
} catch {
print("Error with request: \(error)")
}
return arrRoomNames
}
However you should make a function can throw if this function on its part contains a throwing function
func getAllRoomName() throws -> [String] {
let fetchRequest: NSFetchRequest<SwitchMO> = SwitchMO.fetchRequest()
var arrRoomNames = [String]()
let arrSwitchesMo = try mContext.fetch(fetchRequest)
for switchMo in arrSwitchesMo {
arrRoomNames.append(switchMo.roomName ?? "")
}
return Array(Set(arrRoomNames))
}
If the code still crashes then the managed object context is nil. Declare the context non-optional as suggested in the Core Data template.

Core data context crash

I am working on save image to core data. I used image pick to select image,
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let currentDateTime = Date()
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
self.imagePickedBlock?(image,currentDateTime)
} else {
print("Something went wrong")
}
viewController?.dismiss(animated: true, completion:{
if let addPhotoViewController = self.completionViewController as? AddPhotoViewController {
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else {return }
addPhotoViewController.photoViewModel.image = image
self.viewController?.present(addPhotoViewController, animated: true, completion: nil)
}
})
}
and in addPhotoViewController, I have an confirme button to call the following update database method.
The Photo is an NSManagedObject.
private func updateDatabase(with photoViewModels: [PhotoViewModel]) {
container?.performBackgroundTask { [weak self] context in
for photoViewModel in (self?.photoViewModels)! {
_ = try? Photo.findOrCreatePhoto(matching: photoViewModel, in: context)
}
try? context.save()
self?.printDatabaseStatistics()
}
}
And this is the create NSManagedObject method.
static func findOrCreatePhoto(matching photoViewModel: PhotoViewModel, in context: NSManagedObjectContext) throws -> Photo {
let request : NSFetchRequest<Photo> = Photo.fetchRequest()
// request.predicate if Needed
do {
let matches = try context.fetch(request)
if matches.count > 0 {
return matches[0]
}
} catch {
throw error
}
let photo = Photo(context:context) // the crash line
photo.image = UIImageJPEGRepresentation(photoViewModel.image!, 1)
photo.uploadDate = photoViewModel.createDate
photo.text = photoViewModel.description
// photo.group = try? Group.findOrCreateGroup(matching: photoViewModel, in: context)
return photo
}.
It marks "Enqueued from com.apple.main-thread", I don't really understand where is the problem exactly about the thread, anyone has idea? Don't hesitate if I didn't explain clear enough :)
Thank you for your time.

Core Data entries not updating correctly in swift3

I have a core data entries with certain keys and values stored in the database fetched from API . Now i want to update some core data entries with the updated values So for this i am using the code as below:
func updateAllRecords(responseArray: [Dictionary<String, String>]) {
for settingsObject in responseArray {
if #available(iOS 10.0, *) {
let keys = settingsObject.flatMap() { $0.0 as? String }
let values = settingsObject.flatMap(){ $0.1 as? String}
let request = NSFetchRequest<ApplicationSettings>(entityName: "ApplicationSettings")
do {
let context = persistentContainer.viewContext
let searchResults = try context.fetch(request)
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!) {
settingsKeys.value = values[1]
try context.save()
}
}
} catch {
print ("There was an error")
}
} else {
}
}
}
}
I am calling this function from viewcontroller like this:
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
CoreDataHandler.sharedInstance.updateAllRecords(responseArray: dicArray)
But the entries in the table is not updating correctly for the keys. It is storing the same value for both the keys. See below:
Where i am wrong in this code? Why same values are stored for the keys?
You made mistake here
settingsKeys.value = values[1] here 1 is static
I think you don't require values array separate
just replace this code with my code
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!)
{
settingsKeys.updateValue(settingObject[settingsKeys.key!], forKey:settingsKeys.key!)
try context.save()
}
}
}
For Demo Example
func updateAllRecords(responseArray: [Dictionary<String, String>])
{
for settingsObject in responseArray
{
var dic2 = ["IS_CIVIL_ID_MANDATORY": "xyz", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "xyz"]
let keys = settingsObject.flatMap() {$0.0}
let values = settingsObject.flatMap(){$0.1}
let settingKeys = dic2.flatMap() {$0.0}
for settingsKey in settingKeys
{
dic2.updateValue(settingsObject[settingsKey]!, forKey: settingsKey)
}
print(keys)
print(dic2)
}
}
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
updateAllRecords(responseArray: dicArray)
Output
["TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert", "IS_CIVIL_ID_MANDATORY": "Smith"]

Convert Data to String in Swift 3

I am very new to Swift.
I want to create something like API on Swift for my educational app.
I have this code:
static func getFilm(filmID: Int) -> String {
print("getFilm")
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
var request = URLRequest(url: url)
var returnData: String = ""
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if var responseVar = response, var dataVar = data {
print(responseVar)
returnData = String(data: dataVar, encoding: .utf8)
} else {
print(error)
}
}
task.resume()
return returnData
}
And I try to convert Data to String in this line: returnData = String(data: dataVar, encoding: .utf8)
Swift compiler gives me an error, and change this line to
returnData = String(data: dataVar, encoding: .utf8)!
, when I execute this line I get empty returnData variable.
If I use basic example line
print(String(data: data, encoding: .utf8))
everything will be OK and I can see data in XCode console.
So, how I can convert Data to String?
This is an example using a completion handler:
class func getFilm(filmID: Int, completion: #escaping (String) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(returnData)
} else {
completion("")
}
}
}.resume()
}
And you call it
MyClass.getFilm(filmID:12345) { result in
print(result)
}
In case of an error the completion handler returns an empty string.
MyClass is the enclosing class of getFilm method. Most likely the web service will return JSON, so you might need to deserialize the JSON to an array or dictionary.
In a more sophisticated version create an enum with two cases and associated values
enum ConnectionResult {
case success(String), failure(Error)
}
With a little more effort demonstrating the subtle power of Swift you can return either the converted string on success of the error on failure in a single object.
class func getFilm(filmID: Int, completion: #escaping (ConnectionResult) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
completion(.failure(error!))
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(.success(returnData))
} else {
completion(.failure(NSError(domain: "myDomain", code: 9999, userInfo: [NSLocalizedDescriptionKey : "The data is not converible to 'String'"])))
}
}
}.resume()
}
On the caller side a switch statement separates the cases.
MyClass.getFilm(filmID:12345) { result in
switch result {
case .success(let string) : print(string)
case .failure(let error) : print(error)
}
}
I had this problem, you can't use encoding: .utf8 for unpredictable data. It will return nil every time.
Use this instead:
String(decoding: data, as: UTF8.self)
For anyone coming in future (which are probably not interested in OP's film code?!);
Simply, try something like:
extension Data {
public func toString() -> String {
return String(data: self, encoding: .utf8) ?? "";
}
}
See also my toHex related answer

Resources