Call the same code in every functions of a class - ios

I have a Network Manager class (singleton).
For every API call that the application has to make, a different function from this Network Manager is called.
I would like that every functions of this NM starts by displaying an Activity Indicator until the completion handler of each function is reached: showing the user that a request is being performed.
What would be the best way to do that?
I am using Alamofire pod if that could help out.
Here is an example of one of my API call:
func loadSetting(for user: String, completionHandler: (UserSetting?) -> ()) {
myActivityIndicator.start //a line I'd like to perform in every function of this class
let parameters = ["UUID": user]
_ = Alamofire.request(.POST, "http://www.google.com/users", parameters: parameters)
.responseObject { (response: Response<UserSetting, NSError>) in
if let userSetting = response.result.value {
print("User Setting found remotely")
completionHandler(userSetting)
} else {
print("Couldn't fetch remote User Setting")
completionHandler(nil)
}
myActivityIndicator.stop //a line I'd like to perform in every function of this class
}
}
Thanks.

Why don't you try passing your activity indicator as an argument ?
So from the View controller where you're calling the loadSetting function, you pass in an activity indicator which you hold a reference to, and then inside your loadSetting function, you start/stop it there

Related

How to change a value outside, when inside a Dispatch Semaphore (Edited)

i have a question, and I will tell you as clear as possible:
I need to get an object to my func, create a variable version of it, change some property values one by one, and use new version of it for saving to the cloud. My problem is, when I declare my variable, if I change property values inside my Dispatch Semaphore, the variable outside not changing somehow, there is some problem that I could not understand. Here is the code :
func savePage(model: PageModel, savingHandler: #escaping (Bool) -> Void) {
// some code .....
var page = model // (1) I created a variable from function arg
let newQueue = DispatchQueue(label: "image upload queue")
let semaphore = DispatchSemaphore(value: 0)
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString // (2) urlString value true and exist
page.picURL1 = url // (3) modified the new created "page" object
print(page.picURL1!) // (4) it works, object prints modified
}
}
semaphore.signal()
}
semaphore.wait()
newQueue.async {
if let picURL2 = model.picURL2 {
self.saveImagesToFireBaseStorage(pictureURL: picURL2) { urlString in
let url = urlString
page.picURL2 = url
}
}
semaphore.signal()
}
semaphore.wait()
print(page.picURL1!) //(5) "page" object has the old value?
newQueue.async {
print(page.picURL1!) //(6) "page" object has the old value?
do {
try pageDocumentRef.setData(from: page)
savingHandler(true)
} catch let error {
print("Error writing city to Firestore: \(error)")
}
semaphore.signal()
}
semaphore.wait()
}
I should upload some pictures to the cloud and get their urls, so I can create updated version of the object and save onto the old version on the cloud. But "page" object doesn't change somehow. When inside the semaphore, it prints right value, when outside, or inside another async semaphore block, it prints old value. I am new to the concurrency and could not find a way.
What I tried before :
Using Operation Queue and adding blocks as dependencies.
Creating queue as DispatchQueue.global()
What is the thing I am missing here?
Edit : I added semaphore.wait() after second async call. It actually was in my code but I accidentally deleted it while pasting to the question, thanks to Chip Jarred for pointing it.
UPDATE
This is how I changed the code with async - await:
func savePage(model: PageModel, savingHandler: #escaping () -> Void) {
// Some code
Task {
do {
let page = model
let updatedPage = try await updatePageWithNewImageURLS(page)
// some code
} catch {
// some code
}
}
// Some code
}
private func saveImagesToFireBaseStorage(pictureURL : String?) async -> String? {
//some code
return downloadURL.absoluteString
}
private func updatePageWithNewImageURLS(_ page : PageModel) async throws -> PageModel {
let picUrl1 = await saveImagesToFireBaseStorage(pictureURL: page.picURL1)
let picUrl2 = await saveImagesToFireBaseStorage(pictureURL: page.picURL2)
let picUrl3 = await saveImagesToFireBaseStorage(pictureURL: page.picURL3)
let newPage = page
return try await addNewUrlstoPage(newPage, url1: picUrl1, url2: picUrl2, url3: picUrl3)
}
private func addNewUrlstoPage(_ page : PageModel, url1: String?, url2 : String?, url3 :String?) async throws -> PageModel {
var newPage = page
if let url1 = url1 { newPage.picURL1 = url1 }
if let url2 = url2 { newPage.picURL2 = url2 }
if let url3 = url3 { newPage.picURL3 = url3 }
return newPage
}
So one async function gets a new photo url, for an old url, second async function runs multiple of this first function inside to get new urls for all three urls of the object, then calls a third async function a create and return an updated object with the new values.
This is my first time using async - await.
Let's look at your first async call:
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString // (2) urlString value true and exist
page.picURL1 = url // (3) modified the new created "page" object
print(page.picURL1!) // (4) it works, object prints modified
}
}
semaphore.signal()
}
I would guess that the inner closure, that is the one passed to saveImagesToFireBaseStorage, is also called asynchronously. If I'm right about that, then saveImagesToFireBaseStorage returns almost immediately, executes the signal, but the inner closure has not run yet, so the new value isn't yet set. Then after some latency, the inner closure is finally called, but that's after your "outer" code that depends on page.picURL1 has already been run, so page.picURL1 ends up being set afterwards.
So you need to call signal in the inner closure, but you still have to handle the case where the inner closure isn't called. I'm thinking something like this:
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString
page.picURL1 = url
print(page.picURL1!)
semaphore.signal() // <--- ADDED THIS
}
/*
If saveImagesToFireBaseStorage might not call the closure,
such as on error, you need to detect that and call signal here
in the case as well, or perhaps in some closure that's called in
the failure case. That depends on your design.
*/
}
else { semaphore.signal() } // <--- MOVED INTO `else` block
}
Your second async would need to be modified similarly.
I notice that you're not calling wait after the second async, the one that sets page.picURL2. So you have 2 calls to wait, but 3 calls to signal. That wouldn't affect whether page.picURL1 is set properly in the first async, but it does mean that semaphore will have unbalanced waits and signals at the end of your code example, and the blocking behavior of wait after the third async may not be as you expect it to be.
If it's an option for your project, refactoring to use async and await keywords would resolve the problem in a way that's easier to maintain, because it would remove the need for the semaphore entirely.
Also, if my premise that saveImagesToFireBaseStorage is called asynchronously is correct, you don't really need the async calls at all, unless there is more code in their closures that isn't shown.
Update
In comments, it was revealed the using the solution above caused the app to "freeze". This suggests that saveImagesToFireBaseStorage calls its completion handler on the same queue that savePage(model:savingHandler) is called on, and it's almost certainly DispatchQueue.main. The problem is that DispatchQueue.main is a serial queue (as is newQueue), which means it won't execute any tasks until the next iteration of its run loop, but it never gets to do that, because it calls semaphore.wait(), which blocks waiting for the completion handler for saveImagesToFireBaseStorage to call semaphore.signal. By waiting it prevents the very thing its waiting for from ever executing.
You say in comments that using async/await solved the problem. That's probably the cleanest way, for lots of reasons, not the least of which is that you get compile time checks for a lot of potential problems.
In the mean time, I came up with this solution using DispatchSemaphore. I'll put it here, in case it helps someone.
First I moved the creation of newQueue outside of savePage. Creating a dispatch queue is kind of heavy-weight operation, so you should create whatever queues you need once, and then reuse them. I assume that it's a global variable or instance property of whatever object owns savePage.
The second thing is that savePage doesn't block anymore, but we still want the sequential behavior, preferably without going to completion handler hell (deeply nested completion handlers).
I refactored the code that calls saveImagesToFireBaseStorage into a local function, and made it behave synchronously by using a DispatchSemaphore to block until it's completion handler is called, but only in that local function. I do create the DispatchSemaphore outside of that function so that I can reuse the same instance for both invocations, but I treat it as though it were a local variable in the nested function.
I also have to use a time-out for the wait, because I don't know if I can assume that the completion handler for saveImagesToFireBaseStorage will always be called. Are there failure conditions in which it wouldn't be? The time-out value is almost certainly wrong, and should be considered a place-holder for the real value. You'd need to determine the maximum latency you want to allow based on your knowledge of your app and its working environment (servers, networks, etc...).
The local function uses a key path to allow setting differently named properties of PageModel (picURL1 vs picURL2), while still consolidating the duplicated code.
Here's the refactored savePage code:
func savePage(model: PageModel, savingHandler: #escaping (Bool) -> Void)
{
// some code .....
var page = model
let saveImageDone = DispatchSemaphore(value: 0)
let waitTimeOut = DispatchTimeInterval.microseconds(500)
func saveModelImageToFireBaseStorage(
from urlPath: WritableKeyPath<PageModel, String?>)
{
if let picURL = model[keyPath: urlPath]
{
saveImagesToFireBaseStorage(pictureURL: picURL)
{
page[keyPath: urlPath] = $0
print("page.\(urlPath) = \(page[keyPath: urlPath]!)")
saveImageDone.signal()
}
if .timedOut == saveImageDone.wait(timeout: .now() + waitTimeOut) {
print("saveImagesToFireBaseStorage timed out!")
}
}
}
newQueue.async
{
saveModelImageToFireBaseStorage(from: \.picURL1)
saveModelImageToFireBaseStorage(from: \.picURL2)
print(page.picURL1!)
do {
try self.pageDocumentRef.setData(from: page)
// Assume handler might do UI stuff, so it needs to execute
// on main
DispatchQueue.main.async { savingHandler(true) }
} catch let error {
print("Error writing city to Firestore: \(error)")
// Should savingHandler(false) be called here?
}
}
}
It's important to note that savePage does not block the thread that's called on, which I believe to be DispatchQueue.main. I assume that any code that is sequentially called after a call to savePage, if any, does not depend on the results of calling savePage. Any that does depend on it should be in its savingHandler.
And speaking of savingHandler, I have to assume that it might update the UI, and since the point where it would be called is in a closure for newQueue.async it has to be explicitly called on DispatchQueue.main, so I do that.

How to check if function finished?

I have a function that requests data from an API and puts it in a global struct, so I can read that data from anywhere in the application, all other functions and classes are mainly working in this data. I want to check if that function has finished loading the data before running the other class functions.
The other functions are in different classes.
I cannot use a completion handler (completion: #escaping(_ success: Bool) -> Void) . as this will trigger that function every time it's called, thus it will lead to the data being downloaded over and over again.
Instead, I'm looking for a way to check if that function has completed it's download tasks, then continue with the other functions.
This has to be dynamic, since if let's say, the internet on the device was not working, and then the user connects to the internet, that function will need to run again to try to get the data (could use a refresh button) and notify other classes and function that it finished again so the other functions can start loading the data into the variables
import Foundation
import Alamofire
import SwiftyJSON
struct GlobalUser {
static var json : JSON!
}
func getData() {
// set api url
let LOCATIONAPI_URL = "https://api.darksky.net/forecast/0123456789abcdef9876543210fedcba/42.3601,-71.0589"
// request JSON from url, using Alamofire Library
Alamofire.request(LOCATIONAPI_URL).responseJSON { (response) in
print("data requested")
let result = response.result
// check if respose is valid
if result.isSuccess{
GlobalUser.json = JSON(result.value!)
}
}
}
Sometime ago I came across this.
The idea is to have a completionHandler, that return
func yourFonction(_ parameters: [String: String], completionHandler: (_ result: [String: Any], _ error: Error) -> Void){
//Your Code
completionHandler(result, error)
}
look here (Tutorial), here et even there (Swift Closures Doc) for more information

Call a file-download function from multiple View Controllers; how to return results TO those VC's?

My app will need to download files from my website from several different places in the app, so it seems to make sense to write the function to accomplish the download once, put it in its own class, and call that function from each ViewController. So far, so good, things work. The download is happening, and the downloaded file will print correctly.
The problem comes when the download function goes to send a "success" or "failed" message back to the ViewController that called it, so that the VC can then react accordingly -- update the display, close out the download dialog, whatever. How to make that happen is where I'm stuck.
What I have:
Each of ViewControllerTwo and ViewControllerThree (which are identical for now, other than requesting different files from my server) calls the download function thus:
Downloader.load(url: urlForFileA!, to: localDestinationFileA, callingViewControllerNumber: 2)
The code for the downloader function (which is currently synchronous, but will eventually become asynchronous) looks like this (in its own Downloader class):
class func load(url: URL, to localUrl: URL, callingViewControllerNumber: Int) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Got a file, might be a 404 page...
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success downloading: \(statusCode)")
if statusCode == 404 {
// ERROR -- FILE NOT FOUND ON SERVER
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .fileNotFound, errorMessage: "File not Found, 404 error")
}
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
// SUCCESS!
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .success, errorMessage: "")
} catch (let writeError) {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .movingError, errorMessage: "\(writeError)")
}
} else {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .downloadFailed, errorMessage: "Grave Unexplained Failure")
}
}
task.resume()
}
This part works.
The returnToCaller function is an admittedly ugly (okay, very, very ugly) way to send something back to the calling ViewController:
class func returnToCaller(sourceIdent : Int, successStatus : downloadSuccessStatusEnum, errorMessage : String) {
switch sourceIdent {
case 2:
ViewControllerTwo().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
case 3:
ViewControllerThree().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
default:
fatalError("Unknown sourceIdent of \(sourceIdent) passed to func returnToCaller")
}
}
The problem is that when that returnFromDownload function in the original ViewController is called, it isn't aware of anything in the VC that's loaded -- I go to change the background color of a label, and get a runtime error that the label is nil. The label exists, but this call into the ViewController code is happening in isolation from the running, instantiated VC itself. (Probably the wrong vocabulary there -- apologies.) The code runs and can print but errors out when interacting with anything in the View itself.
The more I work with this, the less confident I am that I'm on the right track here, and my limited experience with Swift isn't enough to see what needs to be happening so that the download function can do all its work "over here" and then return a success/failure message to the calling VC so that the VC can then work with it.
This question seems to be asking something similar; the one answer there doesn't address my (nor, I think, his) root question of how to get code within the presented VC running again with the results of what happened outside the VC (manager approval in his case, download in mine).
Not asking for my code to be rewritten (unless it's a quick fix), but needing to be pointed in the right direction. Many thanks!
What you want can be accomplished pretty easily with a closure.
The first step is to add another parameter to your load method and remove your callingViewController param:
class func load(url: URL, to localUrl: URL, completion: (downloadSuccessStatusEnum, String) -> Void)
This will allow you to call completion instead of returnToCaller when your method completes like so:
DispatchQueue.main.async {
completion(.success, "Insert your error message here")
}
Lastly, to call this method you simply need to call the method like so in your VCs:
Downloader.load(url: nameOfYourURL, to: destinationName) { statusEnum, errorString in
// Insert your code you want after the request completes here
}

Waiting for Asynchronous function call to complete

I know this question has been asked before, but all solutions do not work for me.
I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.
code:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//gradesscrollView.contentSize.height = 3000
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
}
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
}
I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.
Dispatch groups
Semaphore timing
running variables
including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.
EDIT:
Fetch items has been requested,
func fetchItems(completionHandler: #escaping (String?) -> ()) -> () {
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
//Switch to keychain
let username = UserDefaults.standard.object(forKey: "username") as! String?
let password = UserDefaults.standard.object(forKey: "password") as! String?
let usernametoSend = username!
let passwordtoSend = password!
print(usernametoSend)
print(passwordtoSend)
let parameters: Parameters = [
"username": usernametoSend,
"password": passwordtoSend
]
Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
.responseString { response in
completionHandler(String(response.result.value!))
You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.
An async function accepts a job to do, and returns immediately, before the job is done.
in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.
I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.
The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.
I created that function to take a completion handler as a parameter:
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.
The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.
If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)
EDIT:
As others have said, you need to edit your question to show the code for your fetchItems() function.
I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//I moved these lines above the call to fetchItems to make it clear
//that they run before fetchItems' completion closure is executed
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
print("Step 1")
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
print("Step 3")
DispatchQueue.main.async() {
tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
}
}
print("Step 2")
}
You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.
Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.

how to execute some code after execution of another class method code?

I have a common web services class. in that class I have written method getcitydetail(). I want to populate that city detail after getting the result.
I have written following code in viewDidload:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let objWebService = NTMWebServices()
objWebService.getCityDetail()
}
After execution of getcitydetail, I want to do some operation here.
I think we can do it using closure in swift. but I didn't get an idea how to use it.
To use a closure in this situation try something like
func getCityDetail (completion:()->()){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
//background thread, do ws related stuff, the ws should block until finished here
dispatch_async(dispatch_get_main_queue()) {
completion() //main thread, handle completion
}
}
}
then you can use it like
objWebService.getCityDetail {
//do something when the ws is complete
}
You can try any of these:
If getCityDetail() is asynchronous request then, Use Delegate to
respond with the result to the registered class.
Ex:
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error:
NSError!) -> Void in
// Handle incoming data like you would in synchronous request
var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
// Conform to the protocol with the results here
})
iOS Swift Pass Closure as Property? - As here u can use the closure as property and can communicate between the classes

Resources