Here's a struct I've written to convert an NSTimeInterval into a walltime-based dispatch_time_t:
public struct WallTimeKeeper {
public static func walltimeFrom(spec: timespec)->dispatch_time_t {
var mutableSpec = spec
let wallTime = dispatch_walltime(&mutableSpec, 0)
return wallTime
}
public static func timeStructFrom(interval: NSTimeInterval)->timespec {
let nowWholeSecsFloor = floor(interval)
let nowNanosOnly = interval - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
println("walltimekeeper: DEBUG: nowNanosFloor: \(nowNanosFloor)")
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
return thisStruct
}
}
I've been trying to test the accuracy of it in a Playground, but my results are confusing me.
Here's the code in my Playground (with my WallTimeKeeper in the Sources folder):
var stop = false
var callbackInterval: NSTimeInterval?
var intendedTime: NSDate?
var intendedAction: ()->() = {}
func testDispatchingIn(thisManySeconds: NSTimeInterval){
intendedTime = NSDate(timeIntervalSinceNow: thisManySeconds)
intendedAction = stopAndGetDate
dispatchActionAtDate()
loopUntilAfterIntendedTime()
let success = trueIfActionFiredPunctually() //always returns false
}
func dispatchActionAtDate(){
let timeToAct = dateAsDispatch(intendedTime!)
let now = dateAsDispatch(NSDate())
/*****************
NOTE: if you run this code in a Playground, comparing the above two
values will show that WallTimeKeeper is returning times the
correct number of seconds apart.
******************/
dispatch_after(timeToAct, dispatch_get_main_queue(), intendedAction)
}
func loopUntilAfterIntendedTime() {
let afterIntendedTime = intendedTime!.dateByAddingTimeInterval(1)
while stop == false && intendedTime?.timeIntervalSinceNow > 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode,
beforeDate: afterIntendedTime)
}
}
func trueIfActionFiredPunctually()->Bool{
let intendedInterval = intendedTime?.timeIntervalSinceReferenceDate
let difference = intendedInterval! - callbackInterval!
let trueIfHappenedWithinOneSecondOfIntendedTime = abs(difference) < 1
return trueIfHappenedWithinOneSecondOfIntendedTime
}
func dateAsDispatch(date: NSDate)->dispatch_time_t{
let intendedAsInterval = date.timeIntervalSinceReferenceDate
let intendedAsStruct = WallTimeKeeper.timeStructFrom(intendedAsInterval)
let intendedAsDispatch = WallTimeKeeper.walltimeFrom(intendedAsStruct)
return intendedAsDispatch
}
func stopAndGetDate() {
callbackInterval = NSDate().timeIntervalSinceReferenceDate
stop = true
}
testDispatchingIn(3)
...so not only doestrueIfActionFiredPunctually() always returns false, but the difference value--intended to measure the difference between the time the callback fired and the time it was supposed to fire--which in a successful result should be really close to 0, and certainly under 1--instead comes out to be almost exactly the same as the amount of time the callback was supposed to wait to fire.
In summary: an amount of time to wait is defined, and an action is set to fire after that amount of time. When the action fires, it creates a timestamp of the moment it fired. When the timestamp is compared to the value it should be, instead of getting close to zero, we get close to the amount of time we were supposed to wait.
In other words, it appears as if the action passed to dispatch_after is firing immediately, which it absolutely shouldn't!
Is this something wrong with Playgrounds or wrong with my code?
EDIT:
It's the code. Running the same code inside a live app gives the same result. What am I doing wrong?
I figured it out. It's a head-smacker. I'll leave it up in case anyone is having the same problem.
I was using NSDate().timeIntervalSinceReferenceDate to set my walltimes.
Walltimes require NSDate().timeIntervalSince1970!
The dispatch_after tasks all fired instantly because they thought they were scheduled for over forty years ago!
Changing everything to NSDate().timeIntervalSince1970 makes it work perfectly.
Moral: don't use walltimes unless you're sure your reference date is 1970!
Related
I'm trying to understand how to change a struct (or class?) in an array by using reduce. Creating 4 countdown timers, on tap pause the current timer and start the next. So I tried something like this:
var timer1 = CountdownTimer()
// var timer2 = CountdownTimer() etc.
.onTapGesture(perform: {
var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
var last = timers.reduce (false) {
(setActive: Bool, nextValue: CountdownTimer) -> Bool in
if (nextValue.isActive) {
nextValue.isActive = false;
return true
} else {
return false
}
}
if (last) {
var timer = timers[0]
timer.isActive = true
}
})
############# CountdownTimer is a struct ######
struct CountdownTimer {
var timeRemaining: CGFloat = 1000
var isActive: Bool = false
}
This does not work, two errors I'm seeing
the timers in the array are copies of the timers, not the actual timer so changing them doesn't actually change the timers being displayed on screen.
nextValue (i.e. the next timer) can't be changed because it's a let variable in the reduce declaration. I don't know how to change this (or if it's even relevant because presumably it's a copy of the copy of the timer and not the one I actually want to change).
Am I approaching this in a way thats idiomatically wrong for Swift? How should I be changing the original timers?
I agree with Paul about the fact that this should likely all be pulled out into an observable model object. I'd make that model hold an arbitrary list of timers, and the index of the currently active timer. (I'll give an example of that at the end.)
But it's still worth exploring how you would make this work.
First, SwiftUI Views are not the actual view on the screen like in UIKit. They are descriptions of the view on the screen. They're data. They can be copied and destroyed at any time. So they're readonly objects. The way you keep track of their writable state is through #State properties (or similar things like #Binding, #StateObject, #ObservedObject and the like). So your properties need to be marked #State.
#State var timer1 = CountdownTimer()
#State var timer2 = CountdownTimer()
#State var timer3 = CountdownTimer()
#State var timer4 = CountdownTimer()
As you've discovered, this kind of code doesn't work:
var timer = timer1
timer.isActive = true
That makes a copy of timer1 and modifies the copy. Instead, you want WriteableKeyPath to access the property itself. For example:
let timer = \Self.timer1 // Note capital Self
self[keyPath: timer].isActive = true
Finally, reduce is the wrong tool for this. The point of reduce is to reduce a sequence to a single value. It should never have side-effects like modifying the values. Instead, you just want to find the right elements, and then change them.
To do that, it would be nice to be able to easily track "this element and the next one, and the last element is followed by the first." That seems very complicated, but it's surprisingly simple if you include Swift Algorithms. That gives cycled(), which returns a Sequence that repeats its input forever. Why is that useful? Because then you can do this:
zip(timers, timers.cycled().dropFirst())
This returns
(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)
Perfect. With that I can fetch the first active timer (keypath) and its successor, and update them:
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let (current, next) = zip(timers, timers.cycled().dropFirst())
.first(where: { self[keyPath: $0.0].isActive })
{
self[keyPath: current].isActive = false
self[keyPath: next].isActive = true
}
That said, I wouldn't do that. There are subtle requirements here that should be captured in a type. In particular, you have this assumption that there is only one active timer, but nothing enforces that. If that's what you mean, you should make a type that says so. For example:
class TimerBank: ObservableObject {
#Published private(set) var timers: [CGFloat] = []
#Published private(set) var active: Int?
var count: Int { timers.count }
init(timers: [CGFloat]) {
self.timers = timers
self.active = timers.startIndex
}
func addTimer(timeRemaining: CGFloat = 1000) {
timers.append(timeRemaining)
}
func start(index: Int? = nil) {
if let index = index {
active = index
} else {
active = timers.startIndex
}
}
func stop() {
active = nil
}
func cycle() {
if let currentActive = active {
active = (currentActive + 1) % timers.count
print("active = \(active)")
} else {
active = timers.startIndex
print("(init) active = \(active)")
}
}
}
With this, timerBank.cycle() replaces your reduce.
By using the modulus operator ( % ) on Index, we could cycle through last to first without zipping.
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
self[keyPath: timers[onIndex]].isActive = false
let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
self[keyPath: timers[nextIndex]].isActive = true
}
I am trying to write a code where I have got two time[hh:min] data(String type). Need to just compare but the challenge is my code undergones some validations before returning the final values. so the assertion fails sometimes stating expected value is [17:04] but actual is [17:05]. Is there any way where we can use concept of Threshold that upto few minutes (say 2 mins) the comparison will still be valid?
Step one is do not store a thing as something that it is not. If these are times, they should be stored as times. Strings are for representation to the users; underlying storage is for reality.
So now let's store our times as date components:
let t1 = DateComponents(hour:17, minute:4)
let t2 = DateComponents(hour:17, minute:5)
Now it's easy to find out how far apart they are:
let cal = Calendar(identifier: .gregorian)
if let d1 = cal.date(from: t1),
let d2 = cal.date(from: t2) {
let diff = abs(d1.timeIntervalSince(d2))
// and now decide what to do
}
You first need to seprate your string to an array, and then you can compare.
/* That two arrays are A1 and A2 */
let minute1 = Int(A1[0])*60+Int(A1[1])
let minute2 = Int(A2[0])*60+Int(A2[1])
This may help you. I think that #Sweeper did not understand that it is a time, not a date.
You can convert your string to minutes, subtract one from another and check if the absolute value is less than the threshold:
extension String {
var time24hToMinutes: Int? {
guard count == 5, let hours = Int(prefix(2)), let minutes = Int(suffix(2)), Array(self)[2] == ":" else { return nil }
return hours * 60 + minutes
}
func time24hCompare(to other: String, threshold: Int = 2) -> Bool {
guard let lhs = time24hToMinutes, let rhs = other.time24hToMinutes else { return false }
return abs(lhs-rhs) < threshold
}
}
Testing:
"17:02".time24hCompare(to: "17:04") // false
"17:03".time24hCompare(to: "17:04") // true
"17:04".time24hCompare(to: "17:04") // true
"17:05".time24hCompare(to: "17:04") // true
"17:06".time24hCompare(to: "17:04") // false
My application asks user question and get answer, each question has some constant time (for example 30 seconds) for answering. I want show user alert something like "Last (n) seconds..." and if user will not answer in that time - app should skips question.
Wrote some code, using DispatchQueue:
let timePerQuestion = 20
let timeStartAlert = 10
for i in (0..<timeStartAlert) {
DispatchQueue.main.asyncAfter(deadline: (.now() + .seconds(timePerQuestion-timeStartAlert+i))) {
self.failureLabel.text = "Left \(Int(timeStartAlert-i)) seconds..."
self.failureLabel.isHidden = false
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(timePerQuestion)) {
self.failureLabel.text = "Reseting question"
self.failureLabel.isHidden = false
self.quiz.skipQuestion()
self.playNewRound()
self.failureLabel.text = "Sorry, that's not it."
}
It works, but those code executes even if user answered question in time.
So how I can "reset" or "clear" DispatchQueue.main for prevent executing this code if user answered in time?
well you have to use scheduled Timer from class NStimer
the implementations are as follows:
you need to define a timer:
var timer = Timer()//NStimer()in older versions of swift
timer = scheduledTimer(timeInterval: TimeInterval, invocation: NSInvocation, repeats: Bool)
in which the timeInterval is the period before the execution of the required function,invocationis the function you want to run, and repeats indicates if you want the function to repeat invocation until invalidated.
you can read more about NS timers in https://developer.apple.com/reference/foundation/timer
I hope it helps
It's better to use a array/dictionary of bool variables for this kind of problem.
You can have an array or dictionary as given in the following example.
var questionAnswered: [String: Bool] = ["1" : false, "2" : false, "3" : false, "4" : false, "5" : false]
Here, Key = question id and Value = a boolean indicating whether it is answered. You can update it depending on whether user answered the particular question or not. Then, you can use it in your code in the following way:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(timePerQuestion)) {
if let answered: Bool = self.questionAnswered[questionID] {
if !answered {
DispatchQueue.main.async {
self.failureLabel.text = "Reseting question"
self.failureLabel.isHidden = false
self.quiz.skipQuestion()
self.playNewRound()
self.failureLabel.text = "Sorry, that's not it."
}
}
}
}
Feel free to suggest edits to make it better :)
I have an array of subviews and I want to find the lowest tag and the highest tag (~ min and max). I tried to play with the functional approach of Swift and optimized it as much as my knowledge allowed me, but when I do this:
let startVals = (min:Int.max, max:Int.min)
var minMax:(min: Int, max: Int) = subviews.filter({$0 is T2GCell}).reduce(startVals) {
(min($0.min, $1.tag), max($0.max, $1.tag))
}
I still get worse performance (approximately 10x slower) than good ol' for cycle:
var lowest2 = Int.max
var highest2 = Int.min
for view in subviews {
if let cell = view as? T2GCell {
lowest2 = lowest2 > cell.tag ? cell.tag : lowest2
highest2 = highest2 < cell.tag ? cell.tag : highest2
}
}
To be totally precise I am also including snippet of the measuring code. Note that the "after-recalculations" for human readable times is done outside of any measurement:
let startDate: NSDate = NSDate()
// code
let endDate: NSDate = NSDate()
// outside of measuring block
let dateComponents: NSDateComponents = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!.components(NSCalendarUnit.CalendarUnitNanosecond, fromDate: startDate, toDate: endDate, options: NSCalendarOptions(0))
let time = Double(Double(dateComponents.nanosecond) / 1000000.0)
My question is - am I doing it wrong, or this use case is simply not suitable for functional approach?
EDIT
This is is 2x slower:
var extremes = reduce(lazy(subviews).map({$0.tag}), startValues) {
(min($0.lowest, $1), max($0.highest, $1))
}
And this is only 20% slower:
var extremes2 = reduce(lazy(subviews), startValues) {
(min($0.lowest, $1.tag), max($0.highest, $1.tag))
}
Narrowed and squeezed down to very nice performance times, but still not as fast as the for cycle.
EDIT 2
I noticed I left out the filter in previous edits. When added:
var extremes3 = reduce(lazy(subviews).filter({$0 is T2GCell}), startValues) {
(min($0.lowest, $1.tag), max($0.highest, $1.tag))
}
I'm back to 2x slower performance.
In optimized builds, reduce and for should be completely equivalent in performance. However, in unoptimized debug builds, a for loop may beat the reduce version, because reduce will not be specialized and inlined. The filter can be removed, eliminating an unnecessary extra array creation, however that array creation is going to be pretty fast (all it is doing is copying pointers into memory) so that is not really a big deal, eliminating it is more for clarity.
However, I believe part of the problem is that in your reduce, you are calling the .tag property on AnyObject, whereas in your for loop version, you are calling T2GCell.tag. This could make a big difference. You can see this if you break out the filter:
// filtered will be of type [AnyObject]
let filtered = subviews.filter({$0 is T2GCell})
let minMax:(min: Int, max: Int) = filtered.reduce(startVals) {
// so $1.tag is calling AnyObject.tag, not T2GCell.tag
(min($0.min, $1.tag), max($0.max, $1.tag))
}
This means .tag is going to be dynamically bound at runtime, potentially a slower operation.
Here's some sample code that demonstrates the difference. If you compile this will swiftc -O you'll see the statically-bound (or rather not-quite-so dynamically-bound) reduce and the for loop perform pretty much the same:
import Foundation
#objc class MyClass: NSObject {
var someProperty: Int
init(_ x: Int) { someProperty = x }
}
let classes: [AnyObject] = (0..<10_000).map { _ in MyClass(Int(arc4random())) }
func timeRun<T>(name: String, f: ()->T) -> String {
let start = CFAbsoluteTimeGetCurrent()
let result = f()
let end = CFAbsoluteTimeGetCurrent()
let timeStr = toString(Int((end - start) * 1_000_000))
return "\(name)\t\(timeStr)µs, produced \(result)"
}
let runs = [
("Using AnyObj.someProperty", {
reduce(classes, 0) { prev,next in max(prev,next.someProperty) }
}),
("Using MyClass.someProperty", {
reduce(classes, 0) { prev,next in
(next as? MyClass).map { max(prev,$0.someProperty) } ?? prev
}
}),
("Using plain ol' for loop", {
var maxSoFar = 0
for obj in classes {
if let mc = obj as? MyClass {
maxSoFar = max(maxSoFar, mc.someProperty)
}
}
return maxSoFar
}),
]
println("\n".join(map(runs, timeRun)))
Output from this on my machine:
Using AnyObj.someProperty 4115µs, produced 4294310151
Using MyClass.someProperty 1169µs, produced 4294310151
Using plain ol' for loop 1178µs, produced 4294310151
Can't reproduce your exact example, but you can try moving away the filter. The following code should be functionally equivalent to your last attempt.
var extremes4 = reduce(subviews, startValues) {
$1 is T2GCell ? (min($0.lowest, $1.tag), max($0.highest, $1.tag)) : $0
}
Thus you don't iterate twice on subviews. Notice I removed lazy since it appears you always use the entire list.
By the way, IMHO, functional programming can be a very useful approach, but I would think twice before sacrificing code clarity for the only purpose of a fancy functional approach. Thus if a for loop is clearer, and even faster ... just use it ;-) That said, is good for you to experiment with different ways to approach the same problem.
One issue I could think of is that the looping is done twice. First the filter returns an filtered array and then a looping in reduce.
filter(_:)
Returns an array containing the elements of the array for which a provided closure indicates a match.
Declaration
func filter(includeElement: (T) -> Bool) -> [T]
Discussion
Use this method to return a new array by filtering an existing array. The closure that you supply for includeElement: should return a Boolean value to indicate whether an element should be included (true) or excluded (false) from the final collection:
While in the second case there is only one loop.
I am not sure if there is any difference of execution time for 'is' as 'as?' operator.
I'm a beginner in Swift and coding in general. Right now I'm trying to develop the piece of the code to set up the time limit for the action only once a day.
#IBAction func yesButtonPressed(sender: AnyObject) {
//To retrive the control date value. First time it has nil value
var controlDate = NSUserDefaults.standardUserDefaults().objectForKey("controlDate") as? NSDate
//To check current date to compare with
var currentDate = NSDate()
// To check if time interval between controlDate and currentDate is less than 1 day
var timeInterval = controlDate?.timeIntervalSinceNow
var dayInSeconds = 24 * 3600
if timeInterval < dayInSeconds {
//show alert with message "You've done it recently. Pls wait a bit"
} else {
//perfome the action
//update the value of NSUserDefaults.standardUserDefaults().objectForKey("controlDate") with current time stamp
}
}
Instead of checking if the controlTime var has nil value to catch the App first time running I was trying to develop some shorter, universal code for both case, first time and the rest times when the controlDate var will be saved in UserDefaults.
Nevertheless it doesn't work properly (( I'd appreciate your help a lot!
dayInSeconds is the wrong type to make the comparison. Type inference is determining that it should be an Int while timeIntervalSinceNow is a Double under the covers. And thus, you can't definitively compare an Int and a Double with accuracy.
Create dayInSeconds in this way so that it's type is inferred as a Double, rather than an Int.
var controlDate = NSUserDefaults.standardUserDefaults().objectForKey("controlDate") as? NSDate
var currentDate = NSDate()
var timeInterval = controlDate?.timeIntervalSinceNow
//*** HERES THE CHANGE***
var dayInSeconds = 24.0 * 3600
if timeInterval < dayInSeconds {
}
Since you aren't attempting to call a method on timeInterval optional unwrapping is not necessary in this case.
timeInterval is an Optional, you need to unwrap it first
if let ti = timeInterval {
if ti < dayInSeconds {
...
}
}
More on Optionals