This is what I am using (inside a class called Utils.swift)
static let serialQueue = dispatch_queue_create("com.wi-mobile.wmForms", DISPATCH_QUEUE_SERIAL)
However, it seems like I create a new queue everytime I call something like
dispatch_async(UtilsUI.serialQueue)
I would need it to be within another class as I can't instantiate a new queue every time I get into the ViewController I need it to run.
I need to dispatch some network calls, but I need the UI to return immediately after calling the first one, however I need all of them to run serially.
EDIT:
I am using callbacks, and maybe there's where my serialization fails, here's an example:
func sendFormToMiddleware() {
successSending = false
errorSending = false
let seconds: String = String(NSDate().timeIntervalSince1970)
let tbvotos: TbvotosDB = TbvotosDB(survey_id: idForm, transaction_id: "\(WMConfiguration.getTranid())", lu_date: seconds , id_movil: WMConfiguration.getUser(), status: "1")
dispatch_async(UtilsUI.serialQueue) {
self.conectionHandler.doPostWmFormsService(self.attemptToSendForm, service: ConnectionHandler.Service.TBVOTOS, parameters: tbvotos.asArray()!)
}
}
func attemptToSendForm(isOk: Bool, mData: [AnyObject], isLastEntity: Bool) -> Void {
if isOk {
successSending = true
Queries.unifyTransactionIDForCurrentFormQuestions(idForm, tranId: WMConfiguration.getTranid())
let responses = Queries.getResponses(idForm)
dispatch_async(UtilsUI.serialQueue) {
self.conectionHandler.insertRespuestas(self.callbackEnvioRespuestas, responses: responses)
}
self.removeAllOverlays()
self.returnAfterSendingForm()
self.view.userInteractionEnabled = true
self.navigationController?.navigationBar.userInteractionEnabled = true
} else {
self.removeAllOverlays()
self.view.userInteractionEnabled = true
self.navigationController?.navigationBar.userInteractionEnabled = true
errorSending = true
UtilsUI.showAlert(NSLocalizedString("Error al contactar con el Servidor", comment: "Fallo conexión middleware"), self)
}
}
Here doPostWmFormsService's first parameter (attemptToSendForm) is the callback function. I can't figure what other approach should I use in case I am right.
It would need to be on a singleton to persist the same queue, you can use AppDelegate if its a 1 queue simple thing or make a dedicated singleton.
On your app Delegate:
static let serialQueue = dispatch_queue_create("com.wi-mobile.wmForms", DISPATCH_QUEUE_SERIAL)
Then to get it
if let appDel = UIApplication.sharedApplication().delegate as? AppDelegate {
dispatch_async(appDel.serialQueue)
}
I'm not sure how to code it in swift, but from my objective c background I suggest you to use dispatch_once, something like creating a function where you init once your queue and then return it to the caller:
{
var singleton:serialQueue
dispatch_once_t onceToken
dispatch_once(&onceToken,{ in singleton = ...
return singleton;
}
then use the singleton
this will create one object and only one and return it each time u call the function...
The question went out of scope in the comments, being it originally how to create a singleton queue in Swift, I must give credit to Phillip Mills as printing it twice print(UtilsUI.serialQueue) returns the same memory address, thus it is the same object and it's a singleton.
Related
I am using Swift 3 GCD in order to perform some operations in my code. But I'm getting _dispatch_call_block_and_release error often. I suppose the reason behind this error is because different threads modify same variable, but I'm not sure how to fix problem. Here is my code and explanations:
I have one variable which is accessed and modified in different threads:
var queueMsgSent: Dictionary<Date,BTCommand>? = nil
func lock(obj: AnyObject, blk:() -> ()) {
objc_sync_enter(obj)
blk()
objc_sync_exit(obj)
}
func addMsgSentToQueue(msg: BTCommands) {
if queueMsgSent == nil {
queueMsgSent = Dictionary.init()
}
let currentDate = Date()
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.updateValue(msg, forKey: currentDate)
}
}
func deleteMsgSentWithId(id: Int) {
if queueMsgSent == nil { return }
for (date, msg) in queueMsgSent! {
if msg.isAck() == false && msg.getId()! == id {
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.removeValue(forKey: date)
}
}
}
}
func runSent() -> Void {
while(true) {
if queueMsgSent == nil { continue }
for (date, msg) in queueMsgSent! {
if msg.isSent() == false {
mainSearchView?.btCom?.write(str: msg.getCommand()!)
msg.setSent(val: true)
lastMsgSent = Date()
continue
}
if msg.isAck() == true {
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.removeValue(forKey: date)
}
continue
}
}
}
}
I start runSent method as:
DispatchQueue.global().async(execute: runSent)
I need that runSent continuously check some conditions withinn queueMsgSent, and other functions addMsgSentToQueueue and deleteMsgSentWithId are called in main thread id necessary. I am using some locking mechanism but its not working properly
I strongly suggest you to use the DispatchQueue(s) provided by Grand Central Dispatch, they makes multithreading management much easier.
Command
Let's start with your command class
class Command {
let id: String
var isAck = false
var isSent = false
init(id:String) {
self.id = id
}
}
Queue
Now we can build our Queue class, it will provide the following functionalities
This is our class should not be confused with the concept of DispatchQueue!
push a Command into the queue
delete a Command from the queue
start the processing of all the elements into the queue
And now the code:
class Queue {
typealias Element = (date:Date, command:Command)
private var storage: [Element] = []
private let serialQueue = DispatchQueue(label: "serialQueue")
func push(command:Command) {
serialQueue.async {
let newElement = (Date(), command)
self.storage.append(newElement)
}
}
func delete(by id: String) {
serialQueue.async {
guard let index = self.storage.index(where: { $0.command.id == id }) else { return }
self.storage.remove(at: index)
}
}
func startProcessing() {
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
self.processElements()
}
}
private func processElements() {
serialQueue.async {
// send messages where isSent == false
let shouldBeSent = self.storage.filter { !$0.command.isSent }
for elm in shouldBeSent {
// TODO: add here code to send message
elm.command.isSent = true
}
// remove from storage message where isAck == true
self.storage = self.storage.filter { !$0.command.isAck }
}
}
}
How does it work?
As you can see the storage property is an array holding a list of tuples, each tuple has 2 components: Date and Command.
Since the storage array is accesses by multiple threads we need to make sure it is accessed in a thread safe way.
So each time we access storage we wrap our code into this
serialQueue.async {
// access self.storage safely
}
Each code we write into the closure 👆👆👆 shown above is added to our Serial Dispatch Queue.
The Serial Queue does process 1 closure at the time. That's why our storage property is accessed in a thread safe way!
Final consideration
The following block of code is evil
while true {
...
}
It does use all the available CPU time, it does freeze the UI (when executed on the main thread) and discharge the battery.
As you can see I replaced it with
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
self.processElements()
}
which calls self.processElements() every 10 seconds leaving plenty of time to the CPU to process other threads.
Of course it's up to you changing the number of seconds to better fit your scenario.
If you're uncomfortable with the objc mechanisms, you might take a look here. Using that, you create a PThreadMutex for the specific synchronizations you want to coordinate, then use mutex.fastsync{ *your code* } to segregate accesses. It's a simple, very lightweight mechanism using OS-level calls, but you'll have to watch out for creating deadlocks.
The example you provide depends on the object always being the same physical entity, because the objc lock uses the address as the ID of what's being synchronized. Because you seem to have to check everywhere for the existence of queueMsgSent, I'm wondering what the update value routine is doing - if it ever deletes the dictionary, expecting it to be created later, you'll have a potential race as different threads can be looking at different synchronizers.
Separately, your loop in runSent is a spin loop - if there's nothing to do, it's just going to burn CPU rather than waiting for work. Perhaps you could consider revising this to use semaphores or some more appropriate mechanism that would allow the workers to block when there's nothing to do?
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.
I'm new to background operations in iOS, so I'm wondering what is the best way to solve such problem:
I have data, coming from one webservice#1 that needed to be parsed and sent to webservice#2 in background.
I need a background thread, which will be listening for changes in array that stores data from webserivce#1 and when it's not empty, the thread will start uploadOperation, which will process the array and send processed data to webservice#2
Literally, how I see it:
I have DataManager class, presented by a Singleton sharedInstance.
let sharedInstance = DataManager()
It has
public var data: [String]? {
didSet {
processData()
}
}
private var uploadToWebSerivice2Queue: NSOperationQueue?
private override init() {
uploadToWebSerivice2Queue = NSOperationQueue()
uploadToWebSerivice2Queue.maxConcurentOperations = 1
getCachedAndNotSentDataFromDatabase()
}
private func getCachedAndNotSentDataFromDatabase() {
data = ("string 1", "string 2", "string 3", "string 4")
}
private func processData() {
while let lastElement = data!.removeLast {
let processedData = process(lastElement)
let uploadOperation = UploadOperation(processedData) // Data upload opeation
uploadToWebSerivice2Queue!.addOperation(uploadOperation)
}
}
Some data may come from Database where they are cached if they couldn't be send to webservice#2 during the last try. And some data may come from webservice#1 in runtime.
So in other class, let's call it Webservice1DataHandler, I'd do:
DataManager.sharedInstance.data.append("New string to process and upload to webservice#1")
To sum up,
var data will be set after first init() and uploadQueue will start to process that data. Then new string will be appended to var data, that means that processData() method will be invoked and concurecny problems with data array access may occur.
I'm not sure if my algorithm is OK.
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.)
I've written a class called Movie whose initializer takes an integer "id" to retrieve data from the Rotten Tomatoes API:
init(id: Int) {
let movieURL = NSURL(string: "http://api.rottentomatoes.com/api/public/v1.0/movies/\(id).json?apikey=\(apiKey)")!
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: movieURL), queue: NSOperationQueue()) { (response, movieData, error) -> Void in
var movieJson = NSJSONSerialization.JSONObjectWithData(movieData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? [String: AnyObject]
self.id = self.idFromMovieJson(movieJson)
self.title = self.titleFromMovieJson(movieJson)
// ...
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate!.movieDidDownload(self)
})
}
}
If I instantiate a Movie object with a correct id, everything goes as expected. This is what I've written in another class:
var movie = Movie(id: 771351912)
movie.delegate = self
func movieDidDownload(movie: Movie) {
println(movie.title)
}
And this is the output:
Optional("Interstellar")
However, when I try to instantiate a Movie object inside a for-loop like this:
let ids = [771351912, 771380953, 771041011, 13863, 12490, 771311818, 771321699, 11691]
for id in ids {
var movie = Movie(id: id)
movie.delegate = self
}
The print results are not very encouraging:
Optional("Super 8")
Optional("Interstellar")
Optional("Pulp Fiction")
nil
Optional("The Nightmare Before Christmas")
nil
nil
Optional("Nightcrawler")
Worst of all, the println() output is different each time I build and run my code:
Optional("Interstellar")
Optional("Pulp Fiction")
Optional("Super 8")
Optional("Nightcrawler")
Optional("The Nightmare Before Christmas")
nil
nil
nil
I even tried to create different NSOperationQueues with different names to use in the NSURLConnection.sendAsynchronousRequest() method but that didn't work out too:
var queue = NSOperationQueue()
queue.name = "\(id)"
I guess that the problem is related to the fact I'm sending too many requests at the same time. I've placed sleep(1) in the ids for-loop and it actually prints the movie titles properly.
Does anybody know how to asynchronously make multiple requests inside a for-loop?
The swift println() function is asynchronous, and the order of delivery isn't guaranteed, so you may get all kinds of confusing results if you use it like this.
I'd suggest switching to using NSLog() instead, as that will give more consistent results.
Also, creating a new throwaway NSOperationQueue() for each request seems like a bad idea, as the queue may get released before the operation executes. Try using NSOperationQueue.mainQueue() instead.
As an added benefit, using a single queue instead of a different queue for each request should ensure that the movies get downloaded in the order you've requested them.