DispatchGroup with async task - swift

enter code hereI try lot of thing with the dispatch group but I can't obtain stable result.
I use Alamofire for get data since my server. I've write a function in the Helper Class and I use this function in AppDelegate.swift.
I don't know if I put the dispatch group when I call the function so in AppDelegate or I put the dispatch group only in the function in Helper Class.
func alomofireGet(URL: String, onCompletion:#escaping ((JSON) -> Void)) {
// let group = DispatchGroup()
var contentJSON = JSON()
// group.enter()
Alamofire.request(URL, method: .get).responseJSON() { (reponse) in
if reponse.result.isSuccess {
contentJSON = JSON(reponse.result.value!)
} else {
contentJSON = JSON(reponse.result.error!)
}
// group.leave()
}
// group.notify(queue: .main) {
onCompletion(contentJSON)
}
In App delegate, I write a function who call the function in my class.
func connect() {
let group = DispatchGroup()
let _: Bool = KeychainWrapper.standard.removeObject(forKey: "token")
var token = String()
group.enter()
Helper().alomofireGet(URL: "http://192.168.1.19/app_dev.php/login/app") { contenuJSON in
token = contenuJSON["csrfToken"].stringValue
group.leave()
}
group.notify(queue: .main) {
let _: Bool = KeychainWrapper.standard.set(token, forKey: "token")
let t: String? = KeychainWrapper.standard.string(forKey: "token")
print(t!)
}
}
The problem is the variable "t" is empty.
And when I call the keychainWrapper in app delegate, the keychain is empty also.
PS : I have other task, I've just reduce my code

func alomofireGet(URL: String, onCompletion:#escaping ((JSON) -> Void)) {
// let group = DispatchGroup()
var contentJSON = JSON()
// group.enter()
Alamofire.request(URL, method: .get).responseJSON() { (reponse) in
if reponse.result.isSuccess {
contentJSON = JSON(reponse.result.value!)
} else {
contentJSON = JSON(reponse.result.error!)
}
// group.leave()
}
// group.notify(queue: .main) {// where you call wait()function. This blocks the current thread until the group’s tasks have completed.
onCompletion(contentJSON)
}

I try this but is not the solution. I've remove the function in my Helper. I'have juste this function in the app delegate.
func connect(onCompletion : #escaping (String) -> ()) {
let group = DispatchGroup()
var token = String()
let _: Bool = KeychainWrapper.standard.removeObject(forKey: "token")
group.enter()
Alamofire.request("http://192.168.1.19/app_dev.php/login/app", method: .get).responseJSON() { (reponse) in
if reponse.result.isSuccess {
let contentJSON = JSON(reponse.result.value!)
token = contentJSON["csrfToken"].stringValue
} else {
token = "Invalid Token"
}
group.leave()
}
group.notify(queue : DispatchQueue.global()) {
onCompletion(token)
}
}
when I print token, I have a empty message.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 1.5)
connect() { token in
print(token)
}
return true
}

Related

To cancle any api request Using Alamofire in swift

I am calling api using alamofire in swift.it is a search api, when I am searching any text in searchbar I am calling the api. I am calling the api after a delay of 0.75 seconds. I want to cancel the previous request, when new request is there. But I don’t know how to cancel the request. Can anyone help me?
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.reload(_:)), object: searchBar)
perform(#selector(self.reload(_:)), with: searchBar, afterDelay: 0.75)
}
#objc func reload(_ searchBar: UISearchBar) {
guard let query = searchBar.text, query.trimmingCharacters(in: .whitespaces) != "" else {
self.searchLcodeWithText("")
print("nothing to search")
return
}
self.searchLcodeWithText(query)
print(query)
}
private func searchLcodeWithText(_ newText: String){
//Show
startActivityIndicator()
apiService.searchlcode(searchText: newText) { [self] (lcodeData) in
allLcodeArray = []
//Hide
if let lcde = lcodeData{
if lcde.count > 0{
allLcodeArray.append(contentsOf: lcde)
stopActivityIndicator()
}
}
else{
}
stopActivityIndicator()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
import Foundation
import Alamofire
class ApiService{
func searchlcode(searchText: String, completion: #escaping searchLcodeTypeAliasCompletion){
guard let urlrequest = URL(string: SEARCH_URL) else {return}
var parameters = [String:Any]()
parameters.updateValue(searchText, forKey: "lcode_name")
Alamofire.request(urlrequest, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil).responseJSON { [self] (response : DataResponse<Any>) in
if let error = response.result.error {
debugPrint(error.localizedDescription)
return
}else{
guard let data = response.data else { return completion(nil)}
let jsonDecoder = JSONDecoder()
do {
let searchUser = try jsonDecoder.decode(searchLcodeTypeAlias.self, from: data)
print(searchUser)
completion(searchUser)
} catch {
debugPrint(error)
completion(nil)
}
}
}
}
}
Return DataRequest in function then you can cancel specific request.
for Example.
#discardableResult
func login(params: loginRequestModel, completion: #escaping (RequestResult<UserDataResponse, String>) -> Void) -> DataRequest {
params.printJson()
let url = baseUrl + endPoints.login.rawValue
let request = sessionManager.request(url,method: .post, parameters: params, headers: headers)
request.responseDecodable(of: UserDataResponse.self){ (response) in
completion(self.getValidResponse(response))
}
return request
}
usage in viewController
step1 declaration: var loginDataRequest: DataRequest?
step2: initialise data request in function call
step3: action event cancel this request
loginDataRequest.cancel
another approach
you can achieve by using dispatch work Item or operation Queue

Execute func after first func

self.werteEintragen() should start after weatherManager.linkZusammenfuegen() is done. Right now I use DispatchQueue and let it wait two seconds. I cannot get it done with completion func because I dont know where to put the completion function.
This is my first Swift file:
struct DatenHolen {
let fussballUrl = "deleted="
func linkZusammenfuegen () {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString)
}
func perfromRequest(urlString: String)
{
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error != nil{
print(error!)
return
}
if let safeFile = gettingInfo {
self.parseJSON(datenEintragen: safeFile)
}
}
task.resume()
}
}
func parseJSON(datenEintragen: Data) {
let decoder = JSONDecoder()
do {
let decodedFile = try decoder.decode(JsonDaten.self, from: datenEintragen)
TeamOne = decodedFile.data[0].home_name
} catch {
print(error)
}
}
}
And this is my second Swift File as Viewcontroller.
class HauptBildschirm: UIViewController {
func werteEintragen() {
Tone.text = TeamOne
}
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [unowned self] in
self.werteEintragen()
}
}
}
How can I implement this and where?
func firstTask(completion: (_ success: Bool) -> Void) {
// Do something
// Call completion, when finished, success or faliure
completion(true)
}
firstTask { (success) in
if success {
// do second task if success
secondTask()
}
}
You can have a completion handler which will notify when a function finishes, also you could pass any value through it. In your case, you need to know when a function finishes successfully.
Here is how you can do it:
func linkZusammenfuegen (completion: #escaping (_ successful: Bool) -> ()) {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString, completion: completion)
}
func perfromRequest(urlString: String, completion: #escaping (_ successful: Bool) -> ()) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
guard error == nil else {
print("Error: ", error!)
completion(false)
return
}
guard let safeFile = gettingInfo else {
print("Error: Getting Info is nil")
completion(false)
return
}
self.parseJSON(datenEintragen: safeFile)
completion(true)
}
task.resume()
} else {
//can't create URL
completion(false)
}
}
Now, in your second view controller, call this func like this:
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen { [weak self] successful in
guard let self = self else { return }
DispatchQueue.main.async {
if successful {
self.werteEintragen()
} else {
//do something else
}
}
}
}
I highly recommend Google's Promises Framework:
https://github.com/google/promises/blob/master/g3doc/index.md
It is well explained and documented. The basic concept works like this:
import Foundation
import Promises
struct DataFromServer {
var name: String
//.. and more data fields
}
func fetchDataFromServer() -> Promise <DataFromServer> {
return Promise { fulfill, reject in
//Perform work
//This block will be executed asynchronously
//call fulfill() if your value is ready
//call reject() if an error occurred
fulfill(data)
}
}
func visualizeData(data: DataFromServer) {
// do something with data
}
func start() {
fetchDataFromServer
.then { dataFromServer in
visualizeData(data: dataFromServer)
}
}
The closure after "then" will always be executed after the previous Promise has been resolved, making it easy to fulfill asynchronous tasks in order.
This is especially helpful to avoid nested closures (pyramid of death), as you can chain promises instead.

Queue with alamofire

I have a problem with the execution of the tasks when i use Alamofire
I use two time Alamofire, a first to collect a data (token) that I will then use it to send my Post request.
The problem between my two requests, the recovery of the data is done after the second request.
import Foundation
import Alamofire
import SwiftyJSON
class Helper {
func alomofireGet(URL: String) -> JSON {
let queue = DispatchQueue(label: "com.test.com", qos: .background, attributes: .concurrent)
var contenuJSON = JSON()
Alamofire.request(URL, method: .get).responseJSON(queue: queue) { (reponse) in
if reponse.result.isSuccess {
contenuJSON = JSON(reponse.result.value!)
print(contenuJSON)
}
else {
contenuJSON = JSON(reponse.result.error!)
}
}
return contenuJSON
}
func alomofirePost(URL: String, Paramaters: Dictionary<String, Any>) -> JSON {
var contenuJSON = JSON()
Alamofire.request(URL, method: .post, parameters: Paramaters, encoding: JSONEncoding.default).responseJSON { (reponse) in
if reponse.result.isSuccess {
contenuJSON = JSON(reponse.result.value!)
}
else {
contenuJSON = JSON(reponse.result.error!)
}
}
return contenuJSON
}
}
In the new file = DIFFERENCE WITH CONTENT TOKEN
let request = Helper()
#IBOutlet weak var emailText: UITextField!
#IBOutlet weak var passwordText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
}
#IBAction func login(_ sender: Any) {
let contenuJSON = request.alomofireGet(URL: "http://192.168.1.7/app_dev.php/login/app")
print(contenuJSON)
let token = contenuJSON["csrfToken"].stringValue
print(token) // /\ EMPTY
let Paramaters = ["_csrf_token": token, "_password": self.passwordText.text!, "_redirect_url": "", "t_path": "", "_username": self.emailText.text!]
let contenuRequest = request.alomofirePost(URL: "http://192.168.1.7/app_dev.php/login_check", Paramaters: Paramaters)
print(token) // /\ FULL /\
}
}
API call to Alamofire are the async process, hence your alamofireGet and alamofirePost returning just initialized json object - JSON() which does not have any data.
Solution:
You should use #escaping closure, which will hold the control until you get the result from first API call.
func alomofireGet(URL: String, onCompletion:((JSON) -> Void)) {
let queue = DispatchQueue(label: "com.test.com", qos: .background, attributes: .concurrent)
var contentJSON = JSON()
Alamofire.request(URL, method: .get).responseJSON(queue: queue) { (reponse) in
// Load contentJSON with data
if reponse.result.isSuccess {
contenuJSON = JSON(reponse.result.value!)
} else {
contenuJSON = JSON(reponse.result.error!)
}
// Send contentJSON via `onCompletion` block
onCompletion(contenuJSON)
}
}
func alomofirePost(URL: String, Paramaters: Dictionary<String, Any>, onCompletion: #escaping ((_ response: JSON) -> Void)) {
var contenuJSON = JSON()
Alamofire.request(URL, method: .post, parameters: Paramaters, encoding: JSONEncoding.default).responseJSON { (reponse) in
// Load contentJSON with data
if reponse.result.isSuccess {
contenuJSON = JSON(reponse.result.value!)
} else {
contenuJSON = JSON(reponse.result.error!)
}
// Send contentJSON via `onCompletion` block
onCompletion(contenuJSON)
}
}
Call it in your view-controller as:
let usernameStr = self.emailText.text!
let passwordStr = self.passwordText.text!
Helper().alomofireGet(URL: "http://192.168.1.7/app_dev.php/login/app") { contenuJSON in
print(contenuJSON)
DispatchQueue.main.async {
let token = contenuJSON["csrfToken"].stringValue
print(token)
let Paramaters = ["_csrf_token": token, "_password": passwordStr, "_redirect_url": "", "t_path": "", "_username": usernameStr]
Helper().alomofirePost(URL: "http://192.168.1.7/app_dev.php/login_check", Paramaters: Paramaters) { contenuJSON in
print(token)
}
}
}

parse nested completion handlers

I am trying to use a completion handler in a for loop. The problem is that it will keep running the loop before the completion handler returns since it is an async call. Attached is my code. Do I need to use GCD? I am new (obviously)to swift/ios. Any advice would be much appreciated. Bob
for srcTerm in sFields { //search using all search fields
multiQuery (searchTerm: srcTerm) {
if srResult.count < self.lastValue {
self.lastValue = srResult.count
self.lastSearch = srcTerm
}
}
// Do more stuff
}
func multiQuery (searchTerm: String, completion: #escaping ([PFObject]) -> ()) {
var exArray = [PFObject] ()
let query = PFQuery(className: "searchLinks")
do {
query.whereKey("searchTerms", equalTo: searchTerm)
query.findObjectsInBackground (block: { (objects, error)-> Void in
if let error = error {
print("Error Generated: ",error)
return
}
if let objects = objects {
// do stuff
}
completion(self.srResult)
})
}
} // end of function
You could use DispatchGroups, here's an example (taken from https://medium.com/#wilson.balderrama/how-to-use-dispatchgroup-gdc-with-swift-3-35455b9c27e7. Similar to GCD with nested Parse Queries but updated to Swift 3 API):
// Just a sample function to simulate async calls
func run(after seconds: Int, closure: #escaping () -> Void) {
let queue = DispatchQueue.global(qos: .background)
queue.asyncAfter(deadline: .now() + .seconds(seconds)) {
closure()
}
}
let group = DispatchGroup()
group.enter()
run(after: 6) {
print("Hello after 6 seconds")
group.leave()
}
group.enter()
run(after: 3) {
print("Hello after 3 seconds")
group.leave()
}
group.enter()
run(after: 1) {
print("Hello after 1 second")
group.leave()
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
print("All async calls were run!")
}
Using your code:
let group = DispatchGroup()
for srcTerm in sFields { //search using all search fields
group.enter()
multiQuery (searchTerm: srcTerm) {
if srResult.count < self.lastValue {
self.lastValue = srResult.count
self.lastSearch = srcTerm
}
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
// Do something after all async calls are done
}

Swift - Grand Central Dispatch Class

I work on a central point(class), where my data is requested and retrieved.
This class can be called from any view:
override func viewDidLoad() {
super.viewDidLoad()
let group = dispatch_group_create()
var data = RestApiManager(group)
dispatch_group_notify(group, dispatch_get_main_queue()) {
return data
}
}
class RestApiManager {
let user = "user"
let password = "password"
var result: JSON = []
init(group) {
dispatch_group_enter(group)
Alamofire.request(.GET, "http://example.com/api/")
.authenticate(user: user, password: password)
.responseJSON { _, _, result in
switch result {
case .Success(let data):
self.result[] = JSON(data)
case .Failure(_, let error):
print("Request failed with error: \(error)")
}
dispatch_group_leave(group)
}
}
}
But it already fails when initializing the class.
Anybody could help me to design this a proper way??
Thanks and Greetings
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import Foundation
class C {
var txt: String = "start"
init(group: dispatch_group_t) {
dispatch_group_enter(group)
print(txt)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
usleep(200000)
self.txt = "end"
print(self.txt)
dispatch_group_leave(group)
}
}
}
func f() {
let group = dispatch_group_create()
let c = C(group: group)
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
print("notify: \(c) created")
}
}
f()
print("continue running ...")
This works for me without any trouble. You wrote
"But it already fails when initializing the class."
What does it means exactly? Explain your 'failure' in details ...
By the way, I don't understand how do you return your data (return data) from group notification handler ... (generally from any asynchronous code)