Wait for Mouse Click Inside While Loop - swift

I am working with Sprite Kit for an OS X project and I couldn't figure out how to pause my while loop until a mouse click. What I want is something like this:
while (i < 50){
print(i)
i += 1
waitForMouseClick()
}

I solved it using a GCD background queue and a semaphore. Might be helpful for other people reading this.
// CREATE A BACKGROUND QUEUE AND SEMAPHORE
let bgQueue = dispatch_queue_create("myQueue", nil)
let semaphore = dispatch_semaphore_create(0)
override func didMoveToView(view: SKView) {
//DISPATCH CODE BLOCK TO QUEUE
dispatch_async(bgQueue, {
//THIS WILL GRAB YOUR SEMAPHORE
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_NOW)
var i: Int = 0
while (i<50){
i += 1
//BLOCK
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
print(i)
}
})
}
override func mouseDown(theEvent: NSEvent) {
//RELEASE SEMAPHORE
dispatch_semaphore_signal(semaphore)
}

Related

Async data loading swift

I got a function such as scrollViewDidScroll that can trigger many times. And I need to call function loadMoreDataFromRemoteServerIfNeed only single time. How could I do this more elegantly without using any "flag" variables. Maybe I should use DispathGroup|DispatchWorkItem?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if yOffset > offset {
loadMoreDataFromRemoteServerIfNeed()
}
}
func loadMoreDataFromRemoteServerIfNeed() {
DispatchQueue.global(qos: .background).async {
sleep(2)
DispatchQueue.main.async {
// <Insert New Data>
}
}
}
The thing that you are trying to describe — "Do this, but only if you are not told to do it again any time in the next 2 seconds" — has a name. It's called debouncing. This is a well-solved problem in iOS programming, so now that you know its name, you can do a search and find some of the solutions.
While I'm here telling you about this, here's a solution you might not know about. Debouncing is now built in to iOS functionality! Starting in iOS 13, it's part of the Combine framework. I'm now using Combine all over the place: instead of notifications, instead of GCD, instead of Timer objects, etc. It's great!
Here's a Combine-based solution to this type of problem. Instead of a scroll view, suppose we have a button hooked up to an action handler, and we don't want the action handler to do its task unless 2 seconds has elapsed since the last time the user tapped the button:
var pipeline : AnyCancellable?
let pipelineStart = PassthroughSubject<Void,Never>()
#IBAction func doButton(_ sender: Any) {
if self.pipeline == nil {
self.pipeline = pipelineStart
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
.sink { [weak self] _ in self?.doSomething() }
}
self.pipelineStart.send()
}
func doSomething() {
print("I did it!")
}
I'm sure you can readily see how to adapt that to your own use case:
var pipeline : AnyCancellable?
let pipelineStart = PassthroughSubject<Void,Never>()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if yOffset > offset {
if self.pipeline == nil {
self.pipeline = pipelineStart
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
.sink { [weak self] _ in self?.loadMoreDataFromRemoteServerIfNeed()
}
self.pipelineStart.send()
}
}
func loadMoreDataFromRemoteServerIfNeed() {
// <Insert New Data>
}
You can create a flag from DispatchWorkItem to observe loading state e.g.:
var item: DispatchWorkItem?
func loadMoreDataFromRemoteServerIfNeed() {
assert(Thread.isMainThread)
guard item == nil else { return }
item = DispatchWorkItem {
print("loading items")
Thread.sleep(forTimeInterval: 2)
DispatchQueue.main.async {
item = nil
print("insert items")
}
}
DispatchQueue.global().async(execute: item!)
}
NOTE: to synchronise item var you must change its value on the same thread for instance the main thread.
Yes, you could use DispatchWorkItem, keep a reference to the old one, and cancel prior one if necessary. If you were going to do that, I might consider Operation, too, as that handles cancelation even more gracefully and has other advantages.
But that having been said, given that the work that you are dispatching is immediately sleeping for two seconds, this might suggest a completely different pattern, namely a Timer. You can schedule your timer, invalidating previously scheduled timers, if any:
weak var timer: Timer?
func loadMoreDataFromRemoteServerIfNeed() {
// cancel old timer if any
timer?.invalidate()
// schedule what you want to do in 2 seconds
timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
// <Insert New Data>
}
}
FWIW, if you ever find yourself sleeping, you should general consider either timers or asyncAfter. This avoids tying up the global queue’s worker thread. Sleeping is an inefficient pattern.
In this case, keeping a weak reference to the prior timer (if any) is probably the best pattern.

I get "Unexpectedly found nil while implicitly unwrapping an Optional value" when trying to change the value of an NSTextField

I have prior experience in sequential programming in C, but it was in the mid 80's. I am new to OOP, Swift and multithread coding in general. I am writing a small program to better understand all 3. I was able to build a functional program that starts two threads that each count to 200 and then reset to 1 and restart counting in an endless loop. The value of each counter is printed to the console and I have a Start and stop button for each thread that allow me to control them separately. Everything works fine although I would admit that my code is far from perfect (I don't fully respect encapsulation, I have a few global variables that should be made local etc... My main problem is trying to output each thread counter value to a label instead of printing them to the console. When I try to change the content of any of my labels, I get "Unexpectedly found nil while implicitly unwrapping an Optional value"
When I use a similar line of code inside of a pushbutton function it works perfectly.
This is the content of my ViewController.swift file:
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Call Async Task
startProgram()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBOutlet var threadAValueLabel: NSTextField!
#IBOutlet var threadBValueLabel: NSTextField!
#IBAction func threadAStartButton(_ sender: NSButtonCell) {
threadAGoNoGo = 1
self.threadAValueLabel.stringValue = "Start"
}
#IBAction func threadAStopButton(_ sender: NSButton) {
threadAGoNoGo = 0
self.threadAValueLabel.stringValue = "Stop"
}
#IBAction func threadBStartButton(_ sender: NSButton) {
threadBGoNoGo = 1
self.threadBValueLabel.stringValue = "Start"
}
#IBAction func threadBStopButton(_ sender: NSButton) {
threadBGoNoGo = 0
self.threadBValueLabel.stringValue = "Stop"
}
func changethreadALabel(_ message: String) {
self.threadAValueLabel.stringValue = message
}
func changethreadBLabel(_ message: String) {
self.threadBValueLabel.stringValue = message
}
The code creating the error is located in the last 2 methods:
self.threadAValueLabel.stringValue = message
and
self.threadBValueLabel.stringValue = message
While the following code inside of a pushbutton function, Works perfectly.
self.threadBValueLabel.stringValue = "Stop"
The code that creates the two threads is the following:
import Foundation
func startProgram(){
let myViewController: ViewController = ViewController (nibName:nil, bundle:nil)
// Start counting through 200 when Thread A start button is pressed and stop when Thread A Stop button is pressed. When reaching 200, go back to 0 and loop forever
DispatchQueue(label: "Start Thread A").async {
while true { // Loop Forever
var stepA:Int = 1
while stepA < 200{
for _ in 1...10000000{} // Delay loop
if threadAGoNoGo == 1{
print("Thread A \(stepA)")
myViewController.changethreadALabel("South \(stepA)") // Update Thread A value display label
stepA += 1
}
}
stepA = 1
}
}
// Start counting through 200 when Thread B start button is pressed and stop when Thread B Stop button is pressed. When reaching 200, go back to 0 and loop forever
DispatchQueue(label: "Start Thread B").async {
while true { // Loop Forever
var stepB:Int = 1
while stepB < 200{
for _ in 1...10000000{} // Delay loop
if threadBGoNoGo == 1{
print("Tread B \(stepB)")
myViewController.changethreadBLabel("South \(stepB)") // Update Thread B value display label
stepB += 1
}
}
stepB = 1
}
}
}
This is probably very simple to most of you, but I have spent four evenings trying to figure out by myself and searching through this forum, with no success.
New edit:
Thanks to Rob's answer, I was able to progress, but I am hitting another snag. if I move my code to the ViewController class, I seem to be able to access my stringValue variables self.threadAValueLabel.stringValue and self.threadBValueLabel.stringValue, but the below lines generate a "NSControl.stringValue must be used from main thread only" error message:
self.threadAValueLabel.stringValue = message
self.threadBValueLabel.stringValue = message
Although I understand what the error message means, I have tried to find a workaround to this problem for a few hours now and nothing seems to work.
Here is the full code
import Foundation
import Cocoa
public var threadAGoNoGo:Int = 0
public var threadBGoNoGo:Int = 0
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Call Async Task
// Start counting through 200 when Thread A start button is pressed and stop when Thread A Stop button is pressed. When reaching 200, go back to 0 and loop forever
DispatchQueue(label: "Start Thread A").async {
while true { // Loop Forever
var stepA:Int = 1
while stepA < 200{
for _ in 1...10000000{} // Delay loop
if threadAGoNoGo == 1{
print("Thread A \(stepA)")
self.changethreadALabel("Thread A \(stepA)")
stepA += 1
}
}
stepA = 1
}
}
// Start counting through 200 when Thread B start button is pressed and stop when Thread B Stop button is pressed. When reaching 200, go back to 0 and loop forever
DispatchQueue(label: "Start Thread B").async {
while true { // Loop Forever
var stepB:Int = 1
while stepB < 200{
for _ in 1...10000000{} // Delay loop
if threadBGoNoGo == 1{
print("Tread B \(stepB)")
self.changethreadBLabel("Thread B \(stepB)")
stepB += 1
}
}
stepB = 1
}
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBOutlet var threadAValueLabel: NSTextField!
#IBOutlet var threadBValueLabel: NSTextField!
#IBAction func threadAStartButton(_ sender: NSButtonCell) {
threadAGoNoGo = 1
self.threadAValueLabel.stringValue = "Start"
}
#IBAction func threadAStopButton(_ sender: NSButton) {
threadAGoNoGo = 0
self.threadAValueLabel.stringValue = "Stop"
}
#IBAction func threadBStartButton(_ sender: NSButton) {
threadBGoNoGo = 1
self.threadBValueLabel.stringValue = "Start"
}
#IBAction func threadBStopButton(_ sender: NSButton) {
threadBGoNoGo = 0
self.threadBValueLabel.stringValue = "Stop"
}
func changethreadALabel(_ message:String) {
self.threadAValueLabel.stringValue = message
}
func changethreadBLabel(_ message: String) {
self.threadBValueLabel.stringValue = message
}
}
Your startProgram is instantiating a new, duplicative instance of the view controller which does not have any outlets hooked up. Hence, the outlets will be nil and attempts to reference them will generate the error you describe.
If you really wanted to make it a global function, then pass the view controller reference, e.g.:
func startProgram(on viewController: ViewController) {
// var myViewController = ...
// now use the `viewController` parameter rather than the `myViewController` local var
...
}
And then the view controller could pass reference to itself so that the global startProgram knew what instance of the view controller to update:
startProgram(on: self)
Or, better, (a) you shouldn’t have global methods at all; and (b) even if you did, nothing outside the view controller should be updating the view controller’s outlets, anyway. Here the simple solution is to, instead, make startProgram a method of the ViewController class, and then you can refer to the outlets directly.
You have edited your question to put startProgram in the view controller class, as advised above. You then encountered a second, different problem, where the debugger reported:
NSControl.stringValue must be used from main thread only
Yes, if you’re initiated a UI update from a background thread, you must dispatch that update back to the main thread, e.g.:
DispatchQueue.main.async {
self.changethreadALabel("Thread A \(stepA)")
}
See the Main Thread Checker for more information.

How to add DispatchQueue delay in swift while loop?

I'm trying to create a delay inside a while loop. I'm fairly new to this and it's currently just not working. It never fires even once with the dispatch delay, but if I remove the delay it fires repeatedly.
Basically what I'm doing is checking if the velocity of nodes in a SKScene is still moving, if they're still moving, don't end the game. But once they've slowed down, end the game.
func RemainingNodeCheck (complete:() -> Void) {
CountVelocites()
if (IdleVelocity.max()!.isLess(than: 1.0)) {
complete()
} else {
print("Velocity too high, begin wait...")
while !(IdleVelocity.max()?.isLess(than: 1.0))! {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
print("Second passed")
self.CountVelocites()
}
if (IdleVelocity.max()!.isLess(than: 1.0)) {
break
}
}
print("Velocity Calmed down")
complete()
}
}
I believe this might be something to do with threads? Or it's actually just telling the delay to begin waiting for one second so many times that it never gets to call?
UPDATE: I would use a timer, but the RemaingNodeCheck is being called from another part and it's waiting for RemainingNodeCheck to send back complete()
You never want to "wait". But you can set up a repeating timer that checks for some condition, and if so, calls the complete closure (invalidating the timer, if you want). E.g.
class ViewController: UIViewController {
var idleVelocity: ...
weak var timer: Timer?
deinit {
timer?.invalidate()
}
func startCheckingVelocity(complete: #escaping () -> Void) {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self, let maxVelocity = self.idleVelocity.max() else { return }
if maxVelocity < 1 {
timer.invalidate()
complete()
return
}
print("velocity too high...")
}
}
}

Creating semaphore with initial value of 0 make issues with execution

I'm learning GCD and got question about semaphore.
Here is my code:
class ViewController: UIViewController {
var semaphore: dispatch_semaphore_t! = nil
override func viewDidLoad() {
super.viewDidLoad()
semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
print("Entering")
self.semaphoreTask()
print(self.semaphore.debugDescription)
}
semaphoreTask()
print(semaphore.debugDescription)
}
func semaphoreTask() {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
for i in 0...1000 {
print(i)
if i == 1000 {
print("i is equal to 10000!")
}
}
dispatch_semaphore_signal(self.semaphore)
}
If I run this code so nothing from semaphoreTask is printed in the console, but if I change
semaphore = dispatch_semaphore_create(0)
to
semaphore = dispatch_semaphore_create(1)
Everything starts work well.
The question is why should I write dispatch_semaphore_create(1) but not 0?
Thank you!
You can use the semaphore in 2 different ways:
To say when work or a resource is ready.
In this case you start the semaphore at 0. The creator calls signal when something is ready. The consumer calls wait to wait for the expected item / resource.
To limit the number of concurrent operations / requests / usages.
In this case you start the semaphore at a positive value, like 4. The users each call wait and if resource is available they are allowed to continue. If not they are blocked. When each has finished with the resource they call signal.
So, what you see it expected because you're setting the semaphore up as a ready flag but using it as an access limit (because you call wait first).
So I corrected my code to show you how I fixed it (thanks to #Wain).
class ViewController: UIViewController {
var semaphore: dispatch_semaphore_t! = nil
override func viewDidLoad() {
super.viewDidLoad()
semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
print("Entering")
self.semaphoreTask()
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
semaphoreTask()
}
func semaphoreTask() {
print(semaphore.debugDescription)
for i in 0...1000 {
print(i)
if i == 1000 {
print("i is equal to 10000!")
}
}
dispatch_semaphore_signal(self.semaphore)
}
}

How to delete the action inside closure after using dispatch_after function? [duplicate]

I want to run a block of code in 10 seconds from an event, but I want to be able to cancel it so that if something happens before those 10 seconds, the code won't run after 10 seconds have gone by.
I've been using this, but it's not cancellable:
static func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure
)
}
How can I accomplish this?
Swift 3 has DispatchWorkItem:
let task = DispatchWorkItem { print("do something") }
// execute task in 2 seconds
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: task)
// optional: cancel task
task.cancel()
Update for Swift 3.0
Set Perform Selector
perform(#selector(foo), with: nil, afterDelay: 2)
foo method will call after 2 seconds
func foo()
{
//do something
}
To cancel pending method call
NSObject.cancelPreviousPerformRequests(withTarget: self)
Try this (Swift 2.x, see David's answer below for Swift 3):
typealias dispatch_cancelable_closure = (cancel : Bool) -> ()
func delay(time:NSTimeInterval, closure:()->()) -> dispatch_cancelable_closure? {
func dispatch_later(clsr:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(time * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), clsr)
}
var closure:dispatch_block_t? = closure
var cancelableClosure:dispatch_cancelable_closure?
let delayedClosure:dispatch_cancelable_closure = { cancel in
if let clsr = closure {
if (cancel == false) {
dispatch_async(dispatch_get_main_queue(), clsr);
}
}
closure = nil
cancelableClosure = nil
}
cancelableClosure = delayedClosure
dispatch_later {
if let delayedClosure = cancelableClosure {
delayedClosure(cancel: false)
}
}
return cancelableClosure;
}
func cancel_delay(closure:dispatch_cancelable_closure?) {
if closure != nil {
closure!(cancel: true)
}
}
// usage
let retVal = delay(2.0) {
println("Later")
}
delay(1.0) {
cancel_delay(retVal)
}
From Waam's comment here: dispatch_after - GCD in swift?
You need to do this:
class WorkItem {
private var pendingRequestWorkItem: DispatchWorkItem?
func perform(after: TimeInterval, _ block: #escaping VoidBlock) {
// Cancel the current pending item
pendingRequestWorkItem?.cancel()
// Wrap the request in a work item
let requestWorkItem = DispatchWorkItem(block: block)
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + after, execute:
requestWorkItem)
}
}
// Use
lazy var workItem = WorkItem()
private func onMapIdle() {
workItem.perform(after: 1.0) {
self.handlePOIListingSearch()
}
}
References
Link swiftbysundell
Link git
This should work:
var doIt = true
var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
//you have now 10 seconds to change the doIt variable to false, to not run THE CODE
func doSomething()
{
if(doIt)
{
//THE CODE
}
timer.invalidate()
}
I use #sas 's method in some projects, somehow this doesn't work anymore, maybe something changed after Swift 2.1.1. value copy instead of pointer?
the easiest work around method for me is:
var canceled = false
delay(0.25) {
if !canceled {
doSomething()
}
}
For some reason, NSObject.cancelPreviousPerformRequests(withTarget: self) was not working for me. A work around I thought of was coming up with the max amount of loops I'd allow and then using that Int to control if the function even got called.
I then am able to set the currentLoop value from anywhere else in my code and it stops the loop.
//loopMax = 200
var currentLoop = 0
func loop() {
if currentLoop == 200 {
//do nothing.
} else {
//perform loop.
//keep track of current loop count.
self.currentLoop = self.currentLoop + 1
let deadline = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadline) {
//enter custom loop parameters
print("i looped")
self.loop()
}
}
and then elsewhere in your code you can then
func stopLooping() {
currentLoop = 199
//setting it to 199 allows for one last loop to happen. You can adjust based on the amount of loops you want to be able to do before it just stops. For instance you can set currentLoop to 195 and then implement a fade animation while loop is still happening a bit.
}
It's really quite dynamic actually. For instance you can see if currentLoop == 123456789, and it will run infinitely (pretty much) until you set it to that value somewhere else in your code. Or you can set it to a String() or Bool() even, if your needs are not time based like mine were.