URLSession concurrency issue for async calls - ios

I am trying to implement upload mechanism for my application. However, I have a concurrency issue I couldn't resolve. I sent my requests using async/await with following code. In my application UploadService is creating every time an event is fired from some part of my code. As an example I creation of my UploadService in a for loop. The problem is if I do not use NSLock backend service is called multiple times (5 in this case because of loop). But if I use NSLock it never reaches the .success or .failure part because of deadlock I think. Could someone help me how to achieve without firing upload service multiple times and reaching success part of my request.
final class UploadService {
/// If I use NSLock in the commented lines it never reaches to switch result so can't do anything in success or error part.
static let locker = NSLock()
init() {
Task {
await uploadData()
}
}
func uploadData() async {
// Self.locker.lock()
let context = PersistentContainer.shared.newBackgroundContext()
// It fetches data from core data to send it in my request
guard let uploadedThing = Upload.coreDataFetch(in: context) else {
return
}
let request = UploadService(configuration: networkConfiguration)
let result = await request.uploadList(uploadedThing)
switch result {
case .success:
print("success")
case .failure(let error as NSError):
print("error happened")
}
// Self.locker.unlock()
}
}
class UploadExtension {
func createUploadService() {
for i in 0...4 {
let uploadService = UploadService()
}
}
}

A couple of observations:
Never use locks (or wait for semaphores or dispatch groups, etc.) to attempt to manage dependencies between Swift concurrency tasks. This is a concurrency system predicated upon the contract that threads can make forward progress. It cannot reason about the concurrency if you block threads with mechanisms outside of its purview.
Usually you would not create a new service for every upload. You would create one and reuse it.
E.g., either:
func createUploadService() async {
let uploadService = UploadService()
for i in 0...4 {
await uploadService.uploadData(…)
}
}
Or, more likely, if you might use this same UploadService later, do not make it a local variable at all. Give it some broader scope.
let uploadService = UploadService()
func createUploadService() async {
for i in 0...4 {
await uploadService.uploadData(…)
}
}
The above only works in simple for loop, because we could simply await the result of the prior iteration.
But what if you wanted the UploadService keep track of the prior upload request and you couldn’t just await it like above? You could keep track of the Task and have each task await the result of the previous one, e.g.,
actor UploadService {
var task: Task<Void, Never>? // change to `Task<Void, Error>` if you change it to a throwing method
func upload() {
…
task = Task { [previousTask = task] in // capture copy of previous task (if any)
_ = await previousTask?.result // wait for it to finish before starting this one
await uploadData()
}
}
}
FWIW, I made this service with some internal state an actor (to avoid races).

Since creating Task {} is part of structured concurrency it inherits environment (e.g MainThread) from the scope where it was created,try using unstructured concurrency's Task.detached to prevent it from runnning on same scope ( maybe it was called on main thread ) - with creating Task following way:
Task.detached(priority: .default) {
await uploadData()
}

Related

PJSIP random crashing in Swift

I'm build a softphone for iOS using Swift and PJSIP.
The PJSIP documentation states: "PJLIB API should be called from a registered thread, otherwise it will raise assertion such as "Calling pjlib from unknown/external thread...". With GCD, we cannot really be sure of which thread executing the PJLIB function. Registering that thread to PJLIB seems to be a simple and easy solution, however it potentially introduces a random crash which is harder to debug. Here are few possible crash scenarios:
PJLIB's pj_thread_desc should remain valid until the registered thread stopped, otherwise crash of invalid pointer access may occur, e.g: in pj_thread_check_stack().
Some compatibility problems between GCD and PJLIB, see #1837 for more info.
If you want to avoid any possibility of blocking operation by PJLIB (or any higher API layer such as PJMEDIA, PJNATH, PJSUA that usually calls PJLIB), instead of dispatching the task using GCD, the safest way is to create and manage your own thread pool and register that thread pool to PJLIB. Or alternatively, simply use PJSUA timer mechanism (with zero delay), see pjsua_schedule_timer()/pjsua_schedule_timer2() docs for more info."
So what I did was to use the class below found here on another thread in Stack Overflow:
class Worker {
private var thread: Thread?
private let semaphore = DispatchSemaphore(value: 0)
private let lock = NSRecursiveLock()
private var queue = [() -> Void]()
public func enqueue(_ block: #escaping () -> Void) { locked { queue.append(block) }
semaphore.signal()
if thread == nil { thread = Thread(block: work) thread?.start()
}
}
private func work() { while true { semaphore.wait()
let block = locked { queue.removeFirst() } block()
}
}
return block()
}
}
So every time I have to call any function from pjsip I use
worker.enqueue { }
For example:
worker.enqueue {
var threadError: NSError = NSError()
if !createPjSipThread(error: &threadError) {
print(threadError)
}
}
worker.enqueue { var error = NSError()
if !PjApp.shared().start(appDir: "", error: &error {
print(error)
}
}
Without the worker thread, the app crashes constantly (specially on TestFlight/Production Build).
Any ideias on what I'm missing or doing wrong? Thanks in advance.

GKTurnBasedMatch saveCurrentTurnWithMatchData returning an error on every other call

The player takes multiple actions before completing a turn. After each action, I call saveCurrentTurnWIthMatchData, with the match data updated.
[gameMatch saveCurrentTurnWithMatchData: matchData completionHandler: ^(NSError *error){
if (error) {
NSLog(#"Error updating match = %#",error);
}
}];
On every other call I get "Error Domain=GKServerErrorDomain Code=5002 "status = 5002, Unexpected game state version expectedGameStateVersion='null'"
The GKTurnBasedMatch.state = 3 (GKTurnBasedMatchStatusMatching) in every call. I'm not changing this, I just check before the call. I have no idea if this is relevant.
Any suggestion what to try?
the "Unexpected game state version" error happens irregularly and is hard to reproduce -- although i can often reproduce it by calling saveCurrentTurn several times in rapid succession. it would be useful to have clarity from Apple on this since it appears to be server side (but i'm not sure). i wrote a unit test that does stress testing on GKTurnBasedMatch.saveCurrentTurn. it fails irregularly but often up to 20% of the time.
i have no full solution only a partial one. to partially mitigate the problem, you can wrap your saveCurrentTurn calls in a task queue, that way they wait for the previous one to finish. not a solution, but helps.
let dqt:DispatchQueueTask = {
gkTurnBasedMatch.saveCurrentTurn(withMatch:payload) { error in
//handle error
TaskQueue.completion() //step to next task
}
}
TaskQueue.add(task:dqt)
and here is the TaskQueue class i use
import Foundation
/*
Uses the DispatchQueue to execute network commands in series
useful for server commands like GKTurnBasedMatch.saveCurrentTurn(...)
Usage:
let doSomethingThatTakesTime:DispatchQueueTask = {
...
TaskQueue.completion()
}
TaskQueue.add(task: doSomethingThatTakesTime)
*/
typealias DispatchQueueTask = () -> ()
let DispatchQueue_serial = DispatchQueue(label: "org.my.queue.serial")
class TaskQueue {
static var isRunning:Bool = false
static var tasks:[DispatchQueueTask] = []
static func add(task:#escaping DispatchQueueTask) {
tasks.append(task)
run()
}
static func run() {
guard !isRunning else { return }
guard tasks.count > 0 else { return }
let task = tasks.removeFirst()
DispatchQueue_serial.async {
TaskQueue.isRunning = true
task()
}
}
static func completion() {
TaskQueue.isRunning = false
TaskQueue.run()
}
}

Make all endpoints to wait one exact endpoint

I am using Moya to handle HTTP operations and normally I have an refreshToken(). I am checking token if expired or not when a request is about happen but the problem is there can be a scenarios that more than one requests. If they are chained with nested types it is not a problem however, it is not likely all the time.
To be more clear lets say I have request1() and request2() and assume that they execute separate operations and can be triggered anytime(for instance one is called in a viewDidLoad(), other one is called in another viewDidLoad()). when this happens and if the token is expired, my refresh request fails. (statusCode: 400) So, my question is, how can I make provider to wait refresh() operation get done?I mean by provider is other endpoints. I want them to wait refresh() endpoint if it is on.
I will be very appreciated if you suggest a way that will make this easier.
I just set an variable called isTokenRefreshing true when i start the refresh() operation and checked it before making a request. If it was true I stored all the requests in an array and when the refresh() is finished I executed another function which basically makes all the stored requests in a for loop.
If anyone wants to see the code I can share. Just let me know.
EDIT
This where I, NetworkManager, handle all my requests. It is in an Singleton class.
private var awaitingRequests : [NetworkAPI] = []
func makeRequest(_ request: NetworkAPI){
if (Token.sharedInstance.isTokenRefreshing && request.requiresToken) {
self.awaitingRequests.append(request)
return
}
self.provider.request(){ result in ... }
}
func executeWaitedRequests(){
for request in self.awaitingRequests {
self.makeRequest(request)
}
}
NetworkAPI is main enum that I hold my endpoint cases. See the Moya documents if you do not what I am talking about.
And this is where I handle my Token operations.
class Token {
static let sharedInstance = Token()
private init(){}
var isTokenRefreshing: Bool = false
func refresh(_ completion: #escaping ()->()){
self.isTokenRefreshing = true
print("refreshing token")
let queue = DispatchQueue(label: "com.asd.ads.makeRequest", attributes: DispatchQueue.Attributes.concurrent)
queue.sync(flags: .barrier, execute: {
NetworkManager.shared.makeRequest(.refresh(), completionHandler: { (success, error) in
self.isTokenRefreshing = false
if success{
completion()
NetworkManager.shared.executeWaitedRequests()
}
print("refrehing ended!")
})
})
}
}

Swift code being executed asynchronously even while in completion handler

I'm rather new at swift and have been doing some research on how to answer this question myself since I want to learn, but I am completely stumped.
I have a function which requests data from a server, and after the data is received, a completion handler is executed which parses the data. Within the previously mentioned completion handler, another function is called which is passed a completion handler itself.
For some reason, the function call within the function is being being skipped, and being finished after the first completion handler is fully executed. This might make more sense with the code below:
func loadSites(forceDownload: Bool){
self.inspectionSites = MyData.getLocallyStoredInspectionSites()
if self.inspectionSites.count < 1 || forceDownload {
self.http.requestSites({(sitesAcquired, jsonObject) -> Void in
guard sitesAcquired else{
SwiftOverlays.removeAllBlockingOverlays()
MyAlertController.alert("Unable to acquire sites from server or locally")
return
}
let result = jsonObject
for (_,subJson):(String, JSON) in result!.dictionaryValue {
let site = InspectionSite()
site.name = subJson[self.currentIndex]["name"].string!
site.city = subJson[self.currentIndex]["city"].string!
site.address = subJson[self.currentIndex]["address"].string!
site.state = subJson[self.currentIndex]["state"].string!
site.zip = subJson[self.currentIndex]["zip"].stringValue
site.siteId = subJson[self.currentIndex]["id"].string!
objc_sync_enter(self) //SAW A STACKOVERFLOW POST WITH THIS, THOUGHT IT MIGHT HELP
MyLocation.geoCodeSite(site, callback:{(coordinates) -> Void in
print("YO!!!! GEOCODING SITE!")
self.localLat = coordinates["lat"]!
self.localLon = coordinates["lon"]!
})
objc_sync_exit(self)
for type in subJson[self.currentIndex]["inspection_types"]{
let newType = InspectionType()
newType.name = type.1["name"].string!
newType.id = type.1["id"].string!
site.inspectionTypes.append(newType)
}
site.lat = self.localLat
print("HEYY!!!! ASSIGNING COORDS")
site.lon = self.localLon
let address = "\(site.address), \(site.city), \(site.state) \(site.zip)"
site.title = site.name
site.subtitle = address
MyData.persistInspectionSite(site)
self.currentIndex++
}
self.inspectionSites = MyData.getLocallyStoredInspectionSites()
SwiftOverlays.removeAllBlockingOverlays()
self.showSitesOnMap(self.proteanMap)
})
}else{
SwiftOverlays.removeAllBlockingOverlays()
self.showSitesOnMap(self.proteanMap)
}
}
I added those print statements which print "YOOO" and "HEYYY" just so I could see what was being executed first, and "HEYY" is always first. I just need to make sure that the geocoding always happens before the object is persisted. I saw a stackoverflow post which mentioned objc_sync_enter(self) for synchronous operation, but im not even sure if it's what I need.
This is the function which geocodes the site (incase it helps):
class func geoCodeSite(site: InspectionSite, callback: ((coordinates: Dictionary<String, String>)->Void)?) {
let geocoder = CLGeocoder()
let address: String = "\(site.address), \(site.city), \(site.state) \(site.zip)"
print(address)
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
MyLocation.mLat = String(stringInterpolationSegment:placemark.location!.coordinate.latitude)
MyLocation.mLon = String(stringInterpolationSegment:placemark.location!.coordinate.longitude)
MyLocation.coordinates = ["lat":mLat, "lon":mLon]
print(MyLocation.coordinates)
callback?(coordinates: MyLocation.coordinates)
}
})
}
I think the behaviour your seeing is expected. You have two levels of asynchronous methods:
requestSites
geoCodeSite
Since the geoCodeSite method is also asynchronous, its callback is executed well after the line:
MyData.persistInspectionSite(site)
So your problem is how to wait till all InspectionSites have geocoded before persisting the site, right?
Dispatch groups can be used to detect when multiple asynchronous events have finished, see my answer here.
How to Implement Dispatch Groups
dispatch_groups are used to fire a callback when multiple async callbacks have finished. In your case, you need to wait for all geoCodeSite async callbacks to complete before persisting your site.
So, create a dispatch group, firing off your geoCodeSite calls, and implement the dispatch callback inside of which you can persist your geocoded sites.
var myGroup = dispatch_group_create()
dispatch_group_enter(myGroup)
...
fire off your geoCodeSite async callbacks
...
dispatch_group_notify(myGroup, dispatch_get_main_queue(), {
// all sites are now geocoded, we can now persist site
})
Don't forget to add
dispatch_group_leave(myGroup)
inside the closure of geoCodeSite! Otherwise dispatch_group will never know when your async call finish.

Swift dispatch_async from function

How can i wait until function get all data from alamofire get request?
GetData.swift file:
import Foundation
import Alamofire
import SwiftyJSON
import ObjectMapper
func getStartData() -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
}
}
ViewController.swift file:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData() // need to wait until all requests are finished then do print
print(sharedArticle.articleList)
}
}
SingletonObj.swift file:
import Foundation
class BranchSingleton {
var branchList: [Branch] = []
class var sharedInstance: BranchSingleton {
struct Static {
static let instance: BranchSingleton = BranchSingleton()
}
return Static.instance
}
func addBranch(branch: Branch) {
branchList.append(branch)
}
}
class ArticleSingleton {
var articleList: [Article] = []
class var sharedInstance: ArticleSingleton {
struct Static {
static let instance: ArticleSingleton = ArticleSingleton()
}
return Static.instance
}
func addArticle(article: Article) {
articleList.append(article)
}
}
i need to wait until getStartData() finish, then pring singleton array..
How can i do that?
This getStartData contains more than 2 requests, but i just gave example with 2..
You're asking a non-question. There is no reason to "wait". Nor can you. You just do what you do, asynchronously. Meanwhile the interface must stay active; the user must be able to continue to work. Thus there is nothing to "wait" for.
Now, if the question is, how can you send a signal in some elegant way to the rest of your app when all of the requests are done, one good answer is to use NSProgress. All the different requests can contribute to a common NSProgress object. The nice thing is that its fractionCompleted is observable with KVO, so when it comes greater-than-or-equal-to 1.0, you're done.
But you don't actually need the NSProgress; you could just increment or decrement an instance variable that's KVO-observable (being careful about threading, of course). If you know there are n processes, then you could just start a variable at n and have each process decrement it when it completes; a didSet observer on the variable can then take action when we hit zero.
The point is: you don't "wait": you just have all the different activities contribute to some common central value that "knows" when this means we've "finished" and can then take action.
As #Matt says, you can't, and shouldn't, try to wait until Alamofire is done with your request. That's like hiring somebody to run an errand for so you can work and then stopping everything and sitting by the door until they get back. You might as well have run the errand yourself.
Dropping the analogy, you might as well have performed the task synchronously. However, synchronous networking is a very bad idea. It freezes the UI until the network request is complete, which can be a very long wait if something goes wrong.
An async method like Alamofire's request method takes a completion block, a block of code that should be run when the work is finished.
The request method returns immediately, before the request has even been sent to the server, much less completed.
Instead of waiting around for the request to complete, you should refactor your getStartData method to take a completion handler, and use that to respond once the work is done:
func getStartData(completion: () -> void) -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
//At this point the Alamofire .GET request for Config().apiArticle
//is complete. Call our completion block (passed in as a parameter)
completion()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData()
{
//This is a "trailing closure", a block of code passed to getStartData
print("At this point, we've finished getting our data from Alamofire.")
print(sharedArticle.articleList)
}
}
}
Note that your getStartData method makes 2 Alamofire.request() commands in a row. If the second request requires that the first request be finished then you will need to restructure that code so that the second Alamofire request is inside the completion block for the first call. (That's more editing than I'm in the mood to do at the moment.)

Resources