i have a fetch request and i want to use the result to inform other functions. the fetch is
func getExercises() {
guard let managedObjectContext = managedObjectContext else { return }
let userExercise = UserExercise(context: managedObjectContext)
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine == %#", self.routineName)
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let queryResults = try context.fetch(request)
print ("num of results = \(queryResults.count)")
for exercise in queryResults as [NSManagedObject] {
print("Exercise NAME: \(exercise.value(forKey: "name"))")
}
} catch {
print("Error with request: \(error)")
}
The other functions are (capitals added where id want the output to be):
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
let userExercise = THE GET EXERCISE FUNCTIONS OUTPUT(at: indexPath)
and another:
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = THE GET EXERCISE FUNCTIONS OUTPUT {
hasUserExercises = UserExercise.count > 0
}
I thought perhaps id just add queryResults in there and it would be fine, but as thats local to that function i just get errors. Whats the correct way to achieve this?
updated to show errors image
// MARK: - COREDATA FETCH
func getExercises(completion: (([NSManagedObject]?, NSError?) -> Void)?) {
guard let managedObjectContext = managedObjectContext else { return }
let userExercise = UserExercise(context: managedObjectContext)
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine == %#", self.routineName)
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let exercises: [NSManagedObject]?
do {
let queryResults = try context.fetch(request)
print(queryResults)
for exercise in queryResults as [NSManagedObject] {
print("Exercise NAME: \(exercise.value(forKey: "name"))")
}
completion(exercises, nil)
} catch {
print("Error with request: \(error)")
completion(exercises, error)
}
}
}
Use of unresolved identifier 'exercises'
Based on the code you have shown in the image, reason for you first error is that, you have used the property exercises which is not declared yet. exercises in your completion closure's argument will not be accessible within that function. Its a parameter that needs to be passed to that closure.
When you call the completion block, you will have to pass an array of NSManagedObject to it which you construct after you fetch the results. In your case, construct an array within that for..in loop and call the completion block after that loop.
let exercises: [NSManagedObject]?
do {
let queryResults = try context.fetch(request)
print(queryResults)
for exercise in queryResults as [NSManagedObject] {
//Using the result, build the exercises array here
}
completion(exercises, nil)
} catch {
print("Error with request: \(error)")
completion(exercises, error)
}
Nil is not compatible with expected argument type '[NSManagedObject]'
This is because exercises argument is not an optional but you are passing nil to it. You will have to make the exercises argument as optional to take both non-nil and nil values. Change your method signature to below:
func getExercises(completion: ((exercises: [NSManagedObject]?, error: NSError?) -> Void)?)
Note: Make sure you handle nil appropriately in your completion closure you pass to the getExercises function.
Being fetch something that you could do in an async way, the right way would be to use a completion block (callbacks).
So your getExercises function could have a completion block parameter, for instance:
func getExercises(completion: ((exercises: [String], error: NSError?) -> Void)?)
Then, when your data is ready inside the getExercises function, you can invoke the completion block as follows:
completion?(exercises, error)
This question mark before completion is because the completion block is an optional parameter, so you could pass this block or not, up to you.
Maybe some update is required for Swift 3, just check the syntax for completion blocks.
Edit: You will need to pass a closure as an argument for your getExercises function when you call it:
getExercises(completion: { (exercises, error) in
//Do what you want with the result, this is the result of getExercises
print(exercises)
})
Now, inside getExercises you will pass the result to your closure:
func getExercises() {
// ...
do {
// ...
completion(exercises, nil)
} catch {
print("Error with request: \(error)")
completion?([], error)
}
}
Exercises will be of a certain type, not sure what you are handling there, I placed an array of String as example.
Related
I have a service class that makes an api call and stores data into its property. Then my interactor class have a method where I want to make service class api call and when data will be stored - return it. I tried myself to handle this with completion handler and dispatch group, but (I suppose I just missing something) this didn't work. I would be very appreciated if you help me to deal with this problem. Thanks in advance!
Service class:
class PunkApiService{
var beers = [Beer]()
func loadList(at page: Int){
//MARK: - Checks is URL is valid + pagination
guard let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=25") else {
print("Invalid URL")
return
}
//MARK: - Creating URLSession DataTask
let task = URLSession.shared.dataTask(with: url){ data, response, error in
//MARK: - Handling no erros came
guard error == nil else {
print(error!)
return
}
//MARK: - Handling data came
guard let data = data else{
print("Failed to load data")
return
}
do{
let beers = try JSONDecoder().decode([Beer].self, from: data)
self.beers.append(contentsOf: beers)
}
catch{
print("Failed to decode data")
}
}
task.resume()
}
And Interactor class(without completion handler or dispatch group):
class BeersListInteractor:BeersListInteractorProtocol{
private var favoriteBeers = FavoriteBeers()
private var service = PunkApiService()
//MARK: - Load list of Beers
func loadList(at page: Int) -> [Beer]{
service.loadList(at: page)
return service.beers
}
Added: my attempt with completion handler
var beers: [Beer]
func loadList(at page: Int, completion: ()->()){
service.loadList(at: page)
completion()
}
func completion(){
beers.append(contentsOf: service.beers)
}
loadList(at: 1) {
completion()
}
This is what async/await pattern is for, described here. In your case both loadList functions are async, and the second one awaits for the first one:
class PunkApiService {
func loadList(at page: Int) async {
// change function to await for task result
let (data, error) = try await URLSession.shared.data(from: url)
let beers = try JSONDecoder().decode([Beer].self, from: data)
...
return beers
}
}
class BeersListInteractor: BeersListInteractorProtocol {
func loadList(at page: Int) async -> [Beer]{
let beers = await service.loadList(at: page)
return service.beers
}
}
See a good explanation here
I think that you were on the right path when attempting to use a completion block, just didn't do it correctly.
func loadList(at page: Int, completion: #escaping ((Error?, Bool, [Beer]?) -> Void)) {
//MARK: - Checks is URL is valid + pagination
guard let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=25") else {
print("Invalid URL")
completion(nil, false, nil)
return
}
//MARK: - Creating URLSession DataTask
let task = URLSession.shared.dataTask(with: url){ data, response, error in
//MARK: - Handling no erros came
if let error = error {
completion(error, false, nil)
print(error!)
return
}
//MARK: - Handling data came
guard let data = data, let beers = try? JSONDecoder().decode([Beer].self, from: data) else {
completion(nil, false, nil)
return
}
completion(nil, true, beers)
}
task.resume()
}
This is the loadList function, which now has a completion parameter that will have three parameters, respectively the optional Error, the Bool value representing success or failure of obtaining the data, and the actual [Beers] array, containing the data (if any was retrieved).
Here's how you would now call the function:
service.loadList(at: page) { error, success, beers in
if let error = error {
// Handle the error here
return
}
if success, let beers = beers {
// Data was correctly retrieved - and safely unwrapped for good measure, do what you need with it
// Example:
loader.stopLoading()
self.datasource = beers
self.tableView.reloadData()
}
}
Bear in mind the fact that the completion is being executed asynchronously, without stopping the execution of the rest of your app.
Also, you should decide wether you want to handle the error directly inside the loadList function or inside the closure, and possibly remove the Error parameter if you handle it inside the function.
The same goes for the other parameters: you can decide to only have a closure that only has a [Beer] parameter and only call the closure if the data is correctly retrieved and converted.
I am trying to recover a data set from a URL (after parsing a JSON through the parseJSON function which works correctly - I'm not attaching it in the snippet below).
The outcome returns nil - I believe it's because the closure in retrieveData function is processed asynchronously. I can't manage to have the outcome saved into targetData.
Thanks in advance for your help.
class MyClass {
var targetData:Download?
func triggerEvaluation() {
retrieveData(url: "myurl.com") { downloadedData in
self.targetData = downloadedData
}
print(targetData) // <---- Here is where I get "nil"!
}
func retrieveData(url: String, completion: #escaping (Download) -> ()) {
let myURL = URL(url)!
let mySession = URLSession(configuration: .default)
let task = mySession.dataTask(with: myURL) { [self] (data, response, error) in
if error == nil {
if let fetchedData = data {
let safeData = parseJSON(data: fetchedData)
completion(safeData)
}
} else {
//
}
}
task.resume()
}
}
Yes, it’s nil because retrieveData runs asynchronously, i.e. the data hasn’t been retrieved by the time you hit the print statement. Move the print statement (and, presumably, all of the updating of your UI) inside the closure, right where you set self.targetData).
E.g.
func retrieveData(from urlString: String, completion: #escaping (Result<Download, Error>) -> Void) {
let url = URL(urlString)!
let mySession = URLSession.shared
let task = mySession.dataTask(with: url) { [self] data, response, error in
guard
let responseData = data,
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknown(response, data))
}
return
}
let safeData = parseJSON(data: responseData)
DispatchQueue.main.async {
completion(.success(safeData))
}
}
task.resume()
}
Where
enum NetworkError: Error {
case unknown(URLResponse?, Data?)
}
Then the caller would:
func triggerEvaluation() {
retrieveData(from: "https://myurl.com") { result in
switch result {
case .failure(let error):
print(error)
// handle error here
case .success(let download):
self.targetData = download
// update the UI here
print(download)
}
}
// but not here
}
A few unrelated observations:
You don't want to create a new URLSession for every request. Create only one and use it for all requests, or just use shared like I did above.
Make sure every path of execution in retrieveData calls the closure. It might not be critical yet, but when we write asynchronous code, we always want to make sure that we call the closure.
To detect errors, I'd suggest the Result pattern, shown above, where it is .success or .failure, but either way you know the closure will be called.
Make sure that model updates and UI updates happen on the main queue. Often, we would have retrieveData dispatch the calling of the closure to the main queue, that way the caller is not encumbered with that. (E.g. this is what libraries like Alamofire do.)
I'm getting data from Cloud Firestore to populate a ListView. I've managed to get the data into an array, but when I return the array, it shows up empty.
//Edit
I've implemented a completion handler, works perfectly for 'Test Code', but when called in 'func industryPosts' and passed into 'myArray', it returns nil. While 'Test Code' returns data. I'm new to completion handlers, and Swift in general. Kindly let me know what I'm missing. Thanks.
//Edit
I was not able to return the values, but calling industryPosts where I needed to use it worked!
import Foundation
import SwiftUI
class IndustryData {
var _snapshotArray : Array<Any>?
func getSnapshotArray(collectionRef: String, completionHandler: #escaping (Array<Any>?, NSError?) -> ()){
if let snapArray = self._snapshotArray {
completionHandler(snapArray, nil)
} else {
var snapArray : Array<Any> = []
db.collection(collectionRef).getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error - > \(String(describing: error))")
return
}
for document in snapshot.documents {
let item = Industry(avatar: document.get("avatar") as! String, name:document.documentID, tags: document.get("tags") as! String)
snapArray.append(item)
}
self._snapshotArray = snapArray
completionHandler(snapArray, error as NSError?)
}
}
}
}
Then calling the below function where needed
func getposts()-> [Industry] {
let data = IndustryData()
data.getSnapshotArray(collectionRef: "industry") { (snapshotArray, error) in
if snapshotArray != nil {
self.myArray = snapshotArray!
}
}
return myArray as! [Industry]
}
myArray returned Industry Array!
Hello I am having trouble calling my methods to the controller properly as I am getting this error Cannot assign value of type '()' to type '[Version]'. I need help fixing this, thanks.
Swift 3 Method:
var versions : [Version] = []
func isActiveVersion() -> Bool {
let api = versionAPI()
versions = api.getVersionFromAPI(completion: ({_ in }))
for version in versions{
if version["version"] == "1.0.0" {
return true
}
}
}
Swift 3 Call
public class versionAPI {
var versions : [Version] = []
//---------------------------------
// MARK: ENDPOINTS
//---------------------------------
let getVersionEndPoint = "http://127.0.0.1:3000/api/v1/versions"
//---------------------------------
// MARK: REQUESTS
//---------------------------------
func getVersionFromAPI(completion: #escaping ([Version]) -> Void){
let url = URL(string: getVersionEndPoint)
let task = URLSession.shared.dataTask(with: url! as URL) { data, response, error in
guard let data = data, error == nil else {
completion([])
return
}
print(NSString(data: data, encoding: String.Encoding.utf8.rawValue)!)
self.parseVersionsToJSON(data: data)
completion(self.versions)
}
task.resume()
}
func parseVersionsToJSON(data: Data) {
do {
self.versions = []
if let json = try JSONSerialization.jsonObject(with: data) as? [[String:Any]] {
for dic in json {
let version = Version()
version.version = dic["version"] as! String
version.active = dic["active"] as! Bool
self.versions.append(version)
}
}
}
catch{
}
}
}
Your function getVersionFromAPI sets up an asynchronous task and then immediately returns before that task completes, returning void to its caller. This is why you get the error you report.
The [Version] value produced by the asynchronous task is passed by that task to the completion function passed to getVersionFromAPI. The completion function you pass {_ in } does nothing, so the list of versions is simply discarded.
You cannot simply call an asynchronous task, which will complete at some future time, from a synchronous task, getVersionFromAPI in your case, and have that asynchronous task somehow become synchronous and return its future result immediately.
You need to study asynchronous design. Then either redesign your code so the task done by getVersionFromAPI is itself asynchronous, or use one of the standard techniques to block your synchronous method from proceeding until the asynchronous one has completed.
If after revising your design you have trouble with your code ask a new question, showing your code, and someone will undoubtedly help you.
HTH
versions = api.getVersionFromAPI(completion: ({_ in }))
getVersionFromAPI does not return anything. Declare a global struct then pass the data into it and use DispatchQueue when finished to post a NotificationCentre
do {
GlobalStruct.versions = []
if let json = try JSONSerialization.jsonObject(with: data) as? [[String:Any]] {
for dic in json {
let version = Version()
version.version = dic["version"] as! String
version.active = dic["active"] as! Bool
GlobalStruct.versions.append(version)
}
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "gotIt"), object: nil)
}
}
}
your Swift 3 ViewController should have the following:
var versions: [Version] = []
func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(myFunction), name: NSNotification.Name(rawValue: "gotIt"), object: nil)
let api = versionAPI()
api.getVersionFromAPI()
super.viewDidLoad()
}
func myFunction() {
versions = GlobalStruct.versions
if isActiveVersion {
.....
}
}
I'd like to return some values after the long term operation is completed.
But furthermore I'd like to split the logic and the gui.
For example; I have two classes
SomeServices.swift which has a method named "getDataFromService..."
MyTableViewController.swift which will display the result from "getDataFromService"
So, previously in Objective-C I've just add a method in SomeServices like this:
(void)getDataFromService:(void (^)(NSArray *, NSError *))completionBlock{ ...... }
In this method I've just called completionBlock(myData, myError) to return my values to the tableviewcontroller.
What would be the equivalent closure which I have to define in SomeServices.swift and how will it be called in MyTableViewController?
I know how to call a simple closures like this one:
....({
responseData, error in
if(!error){
//Do something
}
})
But I don't have any ideas how to define a closure with a completionBlock equivalent.
Any help would be appreciated
The plus of closures are, that you can pass everything you want. Methods or functions - it doesn't matter.
You can pass a function within the parameters and just call it.
func someFunctionThatTakesAClosure(completionClosure: () -> ()) {
// function body goes here
if(error = false) {
completionClosure()
}
}
//Call it
someFunctionThatTakesAClosure({
//Completions Stuff
println("someFunctionThatTakesAClosure")
});
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/ch/jEUH0.l
The answer is in the language guide:
Assume you want to return a String. This is the syntax
({(responseData: DataClass, error: ErrorClass) -> String in
//do stuff - calculations etc..
return calculatedString
})
Here is an example that takes two strings and concatenates them, and returns the result:
let sumStrings = ({(first: String, second: String) -> String in
return first + " " + second
})
then you can do the following:
sumStrings("Hello","Swift") // "Hello Swift"
Here is how I use a singleton ServiceManager to achieve this.
class ServiceManager: NSObject {
// Static Instance variable for Singleton
static var sharedSessionManager = ServiceManager()
// Function to execute GET request and pass data from escaping closure
func executeGetRequest(with urlString: String, completion: #escaping (Data?) -> ()) {
let url = URL.init(string: urlString)
let urlRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
// Log errors (if any)
if error != nil {
print(error.debugDescription)
} else {
// Passing the data from closure to the calling method
completion(data)
}
}.resume() // Starting the dataTask
}
// Function to perform a task - Calls executeGetRequest(with urlString:) and receives data from the closure.
func downloadMovies(from urlString: String, completion: #escaping ([Movie]) -> ()) {
// Calling executeGetRequest(with:)
executeGetRequest(with: urlString) { (data) in // Data received from closure
do {
// JSON parsing
let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
if let results = responseDict!["results"] as? [[String:Any]] {
var movies = [Movie]()
for obj in results {
let movie = Movie(movieDict: obj)
movies.append(movie)
}
// Passing parsed JSON data from closure to the calling method.
completion(movies)
}
} catch {
print("ERROR: could not retrieve response")
}
}
}
}
Below is the example how I use the singleton class.
ServiceManager.sharedSessionManager.downloadMovies(from: urlBase) { (movies : [Movie]) in // Object received from closure
self.movies = movies
DispatchQueue.main.async {
// Updating UI on main queue
self.movieCollectionView.reloadData()
}
}
I hope this helps anybody looking for the same solution.