I am trying to write code that waits until a variable equals a certain value. Here is the code I have written:
var i = 0
for i in 1...10 {
playtrial()
repeat {
} while (WasItCorrect=="")
WasItCorrect = ""
}
The idea is that the function will call playtrial, wait until the variable WasItCorrect has a value (other than ""), reset WasItCorrect, and then repeat (for a total of 10 times).
The code produces no errors. However, after plastral is done, the program seems to stop responding and does not allow any new input / button pressing.
I assume the problem is that my repeat/while loop goes forever and never gives a chance for anything else to happen and therefore other functions which change the value of WasItCorrect cannot run. As a VisualBasic 6 programmer who is just starting to learn Swift, I would guess I would need the Swift version of DoEvents. Is that true?
Any advice on how to fix this code would be greatly appreciated.
You can use didSet property of the variable. It will be called whenever the value changed.
var i = 0 {
didSet {
print("Hello World.")
if i == certainValue {
// do something
}
}
}
Related
I know, global variables are "not sexy", but I have few in my current project. I played around with Xcode's Thread Sanitizer and found a data race on them. So I tried to make them thread safe.
As I also want a single point of management for this variables. I tried to do the GCD stuff in getter and setter of the variables.
Finally I found a solution that worked, was accepted by the compiler and the Thread Sanitizer was happy .... BUT... this solution looks quite ugly (see below) and is very slow (did a performance test and it was VERY slow).
Yes, I know, if I use classes for this it might be more "swifty", but there must be an easy solution for a thread safe global variable.
So would you be so kind and give hints and suggestions to optimize this attempt? Anyt hint / idea/ suggestion / comment is welcomed!
// I used a "computed variable", to overcome the compiler errors,
// we need a helper variable to store the actual value.
var globalVariable_Value : Int = 0
// this is the global "variable" we worked with
var globalVariable : Int {
// the setter
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable_Value = newValue
}
}
// the getter
get {
// we need a helper variable to store the result.
// inside a void closure you are not allow to "return"
var result : Int = 0
globalDataQueue.sync{
result = globalVariable_Value
}
return result
}
}
// usage
globalVariable = 1
print ("globalVariable == \(globalVariable)")
globalVariable += 1
print ("globalVariable == \(globalVariable)")
// output
// globalVariable == 1
// globalVariable == 2
OOPer asked me to redo the performance tests as found the result strange.
Well, he was right. I did write a simple app (Performance Test App on GitHub) and attached some screenshots.
I run that tests on an iPhone SE with latest IOS. The app was started on the device, not in Xcode. compiler settings were "debug" for all shown test results. I did also test with "full optimizations" (smallest fastest [-Os]), but the results were very similar. I think in that simple tests is not much to optimize.
The test app simply runs the tests described in the answer above. To make it a little bit more realistic, it is doing each test on three parallel async dispatched queues with the qos classes .userInteractive, .default and .background.
There might be better ways to test such things. But for the purpose of this question, I think it's good enough.
I'm happy if anybody would reassess the code and maybe find better test algorithms ... we all would learn from it. I stop my work on this now.
The results are quite strange in my eyes. All three different approaches gives roughly the same performance. On each run there was another "hero", so I assume it is just influenced by other background tasks etc. So even Itai Ferber "nice" solution has in practice no benefit. It's "just" a more elegant code.
And yes, the thread save solution is WAY slower than the not queued solution.
This is the main learning: Yes, it's possible to make global variables thread safe, but there is a significant performance issue with it.
EDIT: I leave this first answer in to keep the history, but a hint of OOPer has lead to a total different view (see next answer).
First of all: I'm quite impressed how fast and well educated the answers flow in (we are on a weekend!)
So the suggestion of Itai Ferber was very good one, and as he asked, I did some performance tests, just to give him something in return ;-)
I run the test with the attached code in a playground. And as you see this is by far not a well designed performance test, it is just a simple test to get a gist of the performance impact. I did several iterations (see table below).
Again: I did it in a Playground, so the absolute times will be much better in a "real" app, but the differences between the tests will be very similar.
Key findings:
interactions shows linear behavior (as expected)
"My" solution (test1) is about 15 times slower than an "un-queued" global variable (test0)
I did a test were I used an additional global variable as the helper variable (test2), this is slightly faster, but not a real break through
The suggested solution from Itai Ferber (test3) is about 6 to 7 times slower that the pure global variable (test0)... so it is twice as fast as "my" solution
So alternative 3 does not only look better, as it doesn't need the overhead for the helper variable it is also faster.
// the queue to synchronze data access, it's a concurrent one
fileprivate let globalDataQueue = DispatchQueue(
label: "com.ACME.globalDataQueue",
attributes: .concurrent)
// ------------------------------------------------------------------------------------------------
// Base Version: Just a global variable
// this is the global "variable" we worked with
var globalVariable : Int = 0
// ------------------------------------------------------------------------------------------------
// Alternative 1: with concurrent queue, helper variable insider getter
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable1_Value : Int = 0
// this is the global "variable" we worked with
var globalVariable1 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable1_Value = newValue
}
}
get {
// we need a helper variable to store the result.
// inside a void closure you are not allow to "return"
var globalVariable1_Helper : Int = 0
globalDataQueue.sync{
globalVariable1_Helper = globalVariable1_Value
}
return globalVariable1_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 2: with concurrent queue, helper variable as additional global variable
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable2_Value : Int = 0
var globalVariable2_Helper : Int = 0
// this is the global "variable" we worked with
var globalVariable2 : Int {
// the setter
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable2_Value = newValue
}
}
// the getter
get {
globalDataQueue.sync{
globalVariable2_Helper = globalVariable2_Value
}
return globalVariable2_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 3: with concurrent queue, no helper variable as Itai Ferber suggested
// "compact" design
var globalVariable3_Value : Int = 0
var globalVariable3 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) { globalVariable3_Value = newValue }
}
get {
return globalDataQueue.sync { globalVariable3_Value }
}
}
// ------------------------------------------------------------------------------------------------
// -- Testing
// variable for read test
var testVar = 0
let numberOfInterations = 2
// Test 0
print ("\nStart test0: simple global variable, not thread safe")
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable
globalVariable += 1
}
let endTime = CFAbsoluteTimeGetCurrent()
let timeDiff = endTime - startTime
print("globalVariable == \(globalVariable), test0 time needed \(timeDiff) seconds")
// Test 1
testVar = 0
print ("\nStart test1: concurrent queue, helper variable inside getter")
let startTime1 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable1
globalVariable1 += 1
}
let endTime1 = CFAbsoluteTimeGetCurrent()
let timeDiff1 = endTime1 - startTime1
print("globalVariable == \(globalVariable1), test1 time needed \(timeDiff1) seconds")
// Test 2
testVar = 0
print ("\nStart test2: with concurrent queue, helper variable as an additional global variable")
let startTime2 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable2
globalVariable2 += 1
}
let endTime2 = CFAbsoluteTimeGetCurrent()
let timeDiff2 = endTime2 - startTime2
print("globalVariable == \(globalVariable2), test2 time needed \(timeDiff2) seconds")
// Test 3
testVar = 0
print ("\nStart test3: with concurrent queue, no helper variable as Itai Ferber suggested")
let startTime3 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable3
globalVariable3 += 1
}
let endTime3 = CFAbsoluteTimeGetCurrent()
let timeDiff3 = endTime3 - startTime3
print("globalVariable == \(globalVariable3), test3 time needed \(timeDiff3) seconds")
Remember the good old days when End would stop everything and goto would take you somewhere without coming back? Well, "End" is essentially what I am trying to do, is cancel the rest of a function.
ie.
func function () {
x = 5
//code to cancel and stop function
x = 0
}
I want this to readout x = 5. The reason I want to do this is because I have numerous functions called from within functions. To keep it simple, is there anyway for this to happen?
From Apple (search for guard):
Early Exit
A guard statement, like an if statement, executes statements depending
on the Boolean value of an expression. You use a guard statement to
require that a condition must be true in order for the code after the
guard statement to be executed.
That would be:
func function() {
x = 5
guard <yourConditionToContinue> else {
return
}
// Code to be executed if <yourConditionToContinue> is met.
}
Heys guys,
I am pretty new into programming and therefore I've followed I course on Udemy to teach me Swift 2.2.
For learning purpose I have been trying to program a BMI-calculator where I have a textfield (which just displays the value) and a slider where I can put my weight in kg. After dragging the slider the value is visible in the textfield. I cannot put a value into the textfield so that it is displayed on the slider!
The same textfield-slider relation is used with the height in cm. Now I created an IBAction the bring my kgSlider.value into my kgField.text and it looks like this:
#IBAction func kgSet(sender: AnyObject) {
kgField.text! = String(Int(kgSlider.value))
}
Thats works fine but I unwrapped (like the teacher in the course) without knowing, if there really is a value. Okay, I know in this case that there will be a value, but I would like to go more real and therefore I tried to use an Optional-Binding to find out, if there is a value instead of directly unwrap it.
Therefore I used the cm.Field and the cm.Slider in my code but it doesn't work for now and I don't know why. The code is the following:
#IBAction func cmSet(sender: AnyObject) {
if let tempCm = String(Int(cmSlider.value)) as String! {
cmField.text = tempCm
}
}
So I created the constant called tempCM which will got the value from the cmSlider, if there is a value. Therefore I casted the cmSlider.value like in the other IBAction into an Int and then into a String. If there is the value it will carry it into the cmField.text. This didn't work, therefore I tried to use the "as String!" statement but know I get always 0 instead of the correct value.
So what am I doing wrong there?
So, this should compile fine and provide you with your desired result.
#IBAction func cmSet(sender: AnyObject) {
if let tempCm = String(Int(cmSlider.value)) {
cmField.text = tempCm
}
}
You could also try this
cmField.text = String(Int(cmSlider.value)) ?? " "
in the second example, you are using the optional operator to say if you can convert this to an Int then that Int to a string set the cmField.text property to its value, otherwise use a blank space as the default value.
Still a beginner with just playing around with some basic functions in swift.
Can someone tell me what is wrong with this code?
import UIKit
var guessInt: Int
var randomNum = arc4random_uniform(10)
if Int(randomNum) == guessInt {
println ("correct")
} else {
println; "no, the number is not. guess again"
}
So far the only error I'm getting is that
guessInt
is being used before being initialized!
I've tried to type everything again but still have the same error.
Thanks in advance.
In Swift you cannot read a value before it's set, which you're doing here:
if Int(randomNum) == guessInt
If you change your declaration from this:
var guessInt:Int
to this:
var guessInt = 6
then your code will work as expected (assuming you want the user's guess to be 6).
You've declared guessInt but now you need to initialize, or set it to some initial value.
For example:
let guessInt = 3
Another option is to declare guessInt as an "Optional", meaning that it can be nil, in fact it will be initialized to nil. This will print "no, ...." until you assign guessInit to a non nil value in the range of values produced by arc4random_uniform(10), but it will compile and run cleanly.
var guessInt:Int? // note the ? after Int this marks it as an Optional
var randomNum = arc4random_uniform(10)
if Int(randomNum) == guessInt {
println ("correct")
} else {
println; "no, the number is not. guess again"
}
Note: "local" meaning contained with in {}
What happens to localVar after func has been expressed?
var constantVarHolder = Int()
func NameOfFunc(){
var localVar = Int()
if X = 0 {
localVar = 3
}
else {
localVar = 4
}
constantVarHolder = localVar
}
Does it become de-initialized, as in no longer using any memory or CPU?
I understand that If I changed the code to..
var singleVar = Int()
func NameOfFunc() {
if X = 0 {
singleVar = 3
}
else {
singleVar = 4
}
}
..would speed up the timing and memory usage for the duration the expression of func.
But after func has completed, would both codes leave your system in identical states?
Your first example doesn't do what you expect it to do (you're shadowing localVar). But I assume you really meant this:
var constantVarHolder = Int()
func NameOfFunc(){
var localVar = Int()
if X = 0 {
localVar = 3
}
else {
localVar = 4
}
constantVarHolder = localVar
}
The short answer is that the optimizer is free to rewrite your code in ways that are logically the same, so your assertion that the second version would be faster than the first (as I've given it) is not correct. They could easily be the identical. The compiler is free to assign local variables to registers, so there may be no RAM usage at all. These variable would be stored on the stack in any case, but it really doesn't matter to this case.
Your question about "CPU" doesn't make sense in this context at all. Variables don't use CPU time. Computation does.
"leave your system in identical states" is an over-broad term. Almost certainly the state will be different in some way. But yes, the global variable will have the same value in either case (assuming you write you code as I've provided it), and all the local variables will have been freed (if they ever existed, which is unlikely).
It's really hard to imagine a case where this question is useful. Even if the optimizer doesn't remove the local stack variable, any tiny time difference of moving an immediate value to the stack and then copying from the stack to RAM is dwarfed by the cost of calling the function in the first place (assuming it isn't inlined). If you're seriously trying to speed up this function, you're looking at this in completely the wrong way.
That said, the way to answer your questions is to look at the assembly output. In Xcode, open the Assistant Editor, and select "Assembly" and then select at the bottom "Profile" or "Release" so it's optimized. If you care about this level of optimization, you'll need to get used to reading that output so you can see what code is actually running.