pausing main thread will async function runs swift - swift

I call the following function in a while loop but the asynchronous code doesn't run even with the dispatch_group being used. Does anyone know what I may be doing incorrectly? *I am trying to essentially get the while loop to wait for the asynchronous code to be executed before continuing.
override func viewDidAppear(animated: Bool) {
if(ended != true) {
if(count2 < 2) {
print("current count is < 2")
} else {
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
asyncCall(playRef, ended: ended, count: count2)
}
print("successful delay")
count2++
//playIsntThere()
}//this is where the else loop ends
count2++
}
func asyncCall(ref:FIRDatabaseReference, ended:Bool, count:Int) {
var count2 = count
let myGroup = dispatch_group_create()
dispatch_group_enter(myGroup)
ref.child(String(count2-2)).observeSingleEventOfType(.Value, withBlock: { (snapshot2) in
var hitterRef = snapshot2.childSnapshotForPath("/Hitter")
var pitcherRef = snapshot2.childSnapshotForPath("/Pitcher")
pitcherEra.text = String(pitcherRef.childSnapshotForPath("/ERA").value!)
})
dispatch_group_leave(myGroup)
dispatch_group_notify(myGroup, dispatch_get_main_queue()) {
print("code completed")
}
print("successful delay")
count2++
}

Related

observeSingleEvent is not called under swift loop

I am trying to call "observeSingleEvent" under swift loop but it get's called after local loop get's done
func fetchAllComments(){
let myGroup = DispatchGroup()
DispatchQueue.main.async {
myGroup.enter()
for index in self.spotVideos {
var id = String(index.videoId)
print(id)
var count = 0
self.videoRef = rootRef.child(id)
self.videoRef!.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount)
myGroup.leave()
}
myGroup.wait()
}
myGroup.notify(queue: .main) {
print("Finished all requests.")
}
}
}
You should use the myGroup.enter() after the loop. Number of enter depends on number of leave. If your loop runs 3 times, there should be 3 enter and 3 leave, In your case you have 1 enter so when it get one leave, it notifies.
for index in self.spotVideos {
myGroup.enter()
...
}
Let's try a simpler approach.
Here's the function to call to get the ball rolling. Note there's a completion closure to handle whatever needs to be done once the children are counted.
func startFetch() {
self.fetchAllComments {
print("done!")
}
}
then the function to iterate over the spotVideos and get their id's. Print the child count of each node and return when done.
func fetchAllComments(completion: #escaping () -> Void) {
let lastIndex = self.spotVideos.count - 1
for (index, vidId) in self.spotVideos.enumerated() {
let ref = rootRef.child(vidId)
ref.observeSingleEvent(of: .value, with: { snapshot in
print("vid id: \(vidId) has \(snapshot.childrenCount) children")
if index == lastIndex {
completion()
}
})
}
}

How do I delay a for loop in swift without interrupting the main thread?

I am trying to read through a string one character a time delaying .1 seconds before moving on to the next character.
I have tried implementing the delay function inside the for loop but it has two problems.
1. The delay is inconsistent and does not take the same amount of time when moving between characters.
2. It disrupts the main thread which I think is the cause of the camera view freezing. However, I think it is also possible that activating the flashlight while the camera is on freezes the signal, causing the glitch.
func delay(_ delay:Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now () + Double(Int64(delay *
Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute:
closure)
}
func scriptReader(){
let str = "10101000001111110000110"
for i in 0..<str.count {
delay(Double(i) * 0.5) {
let index = str.index(str.startIndex, offsetBy: i)
self.flash(number: str[index])
}
}
}
func flash(number: Character){
guard let device = AVCaptureDevice.default(for:
AVMediaType.video) else { return }
guard device.hasTorch else { return }
if number == "0" {
print("off")
do {
try device.lockForConfiguration()
if (device.torchMode == AVCaptureDevice.TorchMode.on) {
device.torchMode = AVCaptureDevice.TorchMode.off
}
device.unlockForConfiguration()
} catch {
print(error)
}
}
if number == "1"{
print("on")
do {
try device.lockForConfiguration()
if (device.torchMode == AVCaptureDevice.TorchMode.off) {
do {
try device.setTorchModeOn(level: 1.0)
} catch {
print(error)
}
}
device.unlockForConfiguration()
} catch {
print(error)
}
}
}
Re your first concern, the use of a series of asyncAfter are going to suffer from “timer coalescing”, where the OS will group future, individually scheduled timers to fire all at once, to make best use of the device battery (the less often the OS needs to wake up the device, the better the battery life). The further out the scheduled timers are, the more coalescing the OS will do.
One can avoid this by using a repeating Timer:
func handle(string: String) {
guard !string.isEmpty else { return }
var index = string.startIndex
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if string[index] == "0" {
// handle "0"
} else {
// handle not "0"
}
index = string.index(after: index)
if index == string.endIndex { timer.invalidate() }
}
}
Something as simple as this may also work:
let threadName = "FlashThread"
let string = "10101000001111110000110"
//Create a custom thread
DispatchQueue(label: threadName).async {
print("Thread Start")
//Start loop
string.forEach { (c) in
print(c)
//Do something on main thread
DispatchQueue.main.async {
self.flash(number: c)
}
//Put your thread to sleep for a second
sleep(1)
}
print("Thread END")
}
I have fixed the issue now. The first problem was solved with the timer code provided by Rob. This stopped the freezing of the main thread and now the code iterates through the String with consistent timing between.
The second problem was fixed by eliminating multiple instances of AVCaptureDevice, once that was fixed the code worked.
Thanks for the help!
I think camera freezes because you call its property every time in for loop, do it once, pass its property to obtain in your func
try this code to fix your glitch
func scriptReader(){
let str = "10101000001111110000110"
guard let device = AVCaptureDevice.default(for: .video) else { return }
guard device.hasTorch else { return }
for i in 0..<str.count {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.5) {
let index = str.index(str.startIndex, offsetBy: i)
self.flash(number: str[index])
}
}
}
func flash(device: AVCaptureDevice, isOn: Bool) {
do {
try device.lockForConfiguration()
device.torchMode = isOn ? AVCaptureDevice.TorchMode.off : device.setTorchModeOn(level: 1.0)
device.unlockForConfiguration()
} catch {
print(error)
}
}

Command Line Tool using Timer() in Swift

I'm writing a command line tool and want to handle various inputs and also want to fire a Timer() on a specific command and also stop it on an other command.
Unfortunately this does not work
import Foundation
class timerTest : NSObject {
var myTimer : Timer? = nil
func startTimer() {
myTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
print("Hello")
})
let runLoop = RunLoop()
runLoop.add(myTimer!, forMode: .defaultRunLoopMode)
RunLoop.current.run()
}
func stopTimer() {
myTimer?.invalidate()
}
}
var a = timerTest()
print("Ready")
while true {
var keyboard = FileHandle.standardInput
var inputData = keyboard.availableData
var string = NSString(data: inputData, encoding:String.Encoding.utf8.rawValue) as! String
string = string.trimmingCharacters(in: .whitespacesAndNewlines)
if string == "timer" { a.startTimer() }
if string == "stop" { a.stopTimer() }
}
You can use something like this:
import Foundation
var shouldKeepRunning = true
class CLIInput:NSObject {
var inputTimer:Timer? = nil
var myTimer:Timer? = nil
func getInput() {
inputTimer = Timer.scheduledTimer(withTimeInterval:1, repeats:false, block: {_ in
var input = readLine()!
input = input.trimmingCharacters(in: .whitespacesAndNewlines)
print("Input: \(input)")
if input == "timer" {
print("Start timer")
self.myTimer = Timer.scheduledTimer(withTimeInterval:1, repeats: true, block: {_ in
print("Hello")
})
}
if input == "stop" {
print("Stop timer")
self.myTimer?.invalidate()
self.myTimer = nil
}
if input == "exit" {
print("Time to exit")
self.inputTimer?.invalidate()
self.inputTimer = nil
shouldKeepRunning = false
return
}
// Otherwise, set up timer again
self.getInput()
})
}
}
print("Start")
var cli = CLIInput()
cli.getInput()
let theRL = RunLoop.current
while shouldKeepRunning && theRL.run(mode: .defaultRunLoopMode, before: .distantFuture) { }
However, any time you are waiting for input from standard input, you'll find that your timer block output does not display because output is blocked till your input is complete. As long as your timer process does not need to do any output, the above might (or might not, depending on your requirements) work ...

Re-execute while loop after completion handler has been returned

I have a while loop i wish to keep executing if the returned number is 0. The problem is currently have is that the loop never stops since it doesnt wait for the completion handler to return its results (which takes a couple seconds). How can i only re execute the while loop after the if statement has been completed?
while loop
var empty = true
override func viewDidLoad() {
repeat {
str = iFunctions.generateRandomStringWithLengthOf(4) as String
iFunctions.getServerData(str){(msg)
in
if (msg > 0){
self.empty = false
print("not empty")
}
else{
print("empty")
}
self.count = msg!
print(self.count)
}
} while(empty)
}
function
func getServerData(q: String, completionHandler: (Int?) -> ()) -> () {
let params = [
"term": q
]
Alamofire.request(.GET, "URL", parameters: params)
.responseJSON { response in
if response.result.error == nil {
let json = JSON(response.result.value!)
//add data to struct
completionHandler(json["results"].count)
}
}
}
You shouldn't do this with a loop. That's the whole point of Alamofire, so you don't have to deal with threads and loops. You're working with high level instructions: callbacks.
It's actually very easy to solve your problem:
var empty = true
override func viewDidLoad() {
self.tryGettingDataFromServer()
}
func tryGettingDataFromServer(){
str = iFunctions.generateRandomStringWithLengthOf(4) as String
iFunctions.getServerData(str){(msg) in
if (msg > 0){
self.empty = false
print("not empty")
}
else{
// The closure keeps a reference to self and calls
// the tryGettingDataFromServer again if "empty"
// It will happen infinitely until "not empty"
self.tryGettingDataFromServer()
}
self.count = msg!
print(self.count)
}
}

NSURLSession, Completion Block, Swift

Im working with NSURLSession. I have an array with restaurants and i'm requesting the dishes for every restaurant in the array to the api. The dataTask works,i'm just having a real hard time trying to call a method only when the all dataTasks are finished.
self.findAllDishesOfRestaurants(self.restaurantsNearMe) { (result) -> Void in
if result.count != 0 {
self.updateDataSourceAndReloadTableView(result, term: "protein")
} else {
print("not ready yet")
}
}
the self.updateDataSourceAndREloadTableView never gets called, regardless of my completion block. Here is my findAllDishesOfRestaurants function
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
self.allDishes.addObjectsFromArray(dishesArray as [AnyObject])
print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray)
})
completion(result:allDishesArray)
}
}
}
And here is my the function where i perform the dataTasks.
func getDishesByRestaurantName(restaurant:Resturant, completion:(result:NSArray) ->Void) {
var restaurantNameFormatted = String()
if let name = restaurant.name {
for charachter in name.characters {
var newString = String()
var sameCharacter:Character!
if charachter == " " {
newString = "%20"
restaurantNameFormatted = restaurantNameFormatted + newString
} else {
sameCharacter = charachter
restaurantNameFormatted.append(sameCharacter)
}
// print(restaurantNameFormatted)
}
}
var urlString:String!
//not to myself, when using string with format, we need to igone all the % marks arent ours to replace with a string, otherwise they will be expecting to be replaced by a value
urlString = String(format:"https://api.nutritionix.com/v1_1/search/%#?results=0%%3A20&cal_min=0&cal_max=50000&fields=*&appId=XXXXXXXXXappKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXX",restaurantNameFormatted)
let URL = NSURL(string:urlString)
let restaurantDishesArray = NSMutableArray()
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithURL(URL!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
do {
let anyObjectFromResponse:AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
if let asNSDictionary = anyObjectFromResponse as? NSDictionary {
let hitsArray = asNSDictionary.valueForKey("hits") as? [AnyObject]
for newDictionary in hitsArray! as! [NSDictionary]{
let fieldsDictionary = newDictionary.valueForKey("fields") as? NSDictionary
let newDish = Dish.init(dictionary:fieldsDictionary!, restaurant: restaurant)
restaurantDishesArray.addObject(newDish)
}
}
completion(result:restaurantDishesArray)
} catch let error as NSError {
print("failed to connec to api")
print(error.localizedDescription)
}
}
dataTask.resume()
}
Like i said before, I need to wait until the fun findAllDishesOfRestaurants is done. I tried writing my completion blocks but I'm not sure I'm doing it right. Any help is greatly appreciated. Thank
The problem is that you are calling the completion method in findAllDishesOfRestaurants before al tasks are complete. In fact, you are calling it once for each restaurant in the list, which is probably not what you want.
My recommendation would be for you to look into NSOperationQueue for two reasons:
It will let you limit the number of concurrent requests to the server, so your server does not get flooded with requests.
It will let you easily control when all operations are complete.
However, if you are looking for a quick fix, what you need is to use GCD groups dispatch_group_create, dispatch_group_enter, dispatch_group_leave, and dispatch_group_notify as follows.
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let group = dispatch_group_create() // Create GCD group
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
dispatch_group_enter(group) // Enter group for this restaurant
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
// self.allDishes.addObjectsFromArray(dishesArray as [AnyObject]) <-- do not do this
// print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray) <-- No need for this, remove
dispatch_group_leave(group) // Leave group, marking this restaurant as complete
})
// completion(result:allDishesArray) <-- Do not call here either
}
}
// Wait for all groups to complete
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
completion(result:allDishesArray)
}
}