How to synchronize different actions in Swift Language for IOS - ios

I created a timer in one class, and tried to do something else in another class while timer works, and do other thing while timer stops. For example, show every second when timer works. I simplified the code as below. How to realize that?
import Foundation
import UIView
class TimerCount {
var timer: NSTimer!
var time: Int!
init(){
time = 5
timer = NSTimer.scheduledTimerWithTimeInterval( 1.0 , target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update(){
if(time > 0) {
time = time - 1
// do something while timer works
}
else{
timer.invalidate()
timer = nil
time = 5
}
}
}
class Main: UIView {
var Clock: TimerCount!
override func viewDidLoad() {
Clock = TimerCount()
//? do something else while clock works
// ? do other things while clock stops
// FOR EXAMPLE: show every second when timer works
if(Clock.time > 0){
println(Clock.time)
}else{
println("clocker stops")
}
}
}

viewDidLoad is most likely only going to be called once. You could simply make the update method be in your Main object and then pass that instance of main and that update method into the scheduledTimerWithTimerInterval call. Otherwise you need a new method in the Main class to call from the timerCount class and pass in the int for the current time.
This is in your Main class:
func updateMethodInMain(timeAsInt: Int){
//do stuff in Main instance based on timeAsInt
}
This is what you have in your timer class:
func update(){
if(time > 0) {
time = time - 1
instanceNameForMain.updateMethodInMain(time)
}
else{
timer.invalidate()
timer = nil
time = 5
}
}
}

Related

Timer and dependancy injection

I've just started with Swift and using MVVM with dependency injection.
In my ViewModel I have Timer that handles refreshing the data. I've simplified the code a little for clarity.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = ViewModel()
}
}
class ViewModel: NSObject {
private var timer: Timer?
override init() {
super.init()
setUpTimer()
}
func setUpTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true){_ in
self.refreshData()
}
}
func refreshData() {
//refresh data
print("refresh data")
}
}
I want to use dependency injection to pass the Timer into the ViewModel so that I can control the timer when doing unit tests and make it call immediately.
So passing the Timer is pretty simple. How can I pass a Timer in to ViewModel that has the ability to call the refreshData() belonging to ViewModel. Is there a trick in Swift that allows this?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true){_ in
// call refreshData() from the class ViewModel
}
var viewModel = ViewModel(myTimer:timer)
}
}
class ViewModel: NSObject {
private var timer: Timer?
init(myTimer:Timer) {
super.init()
//setUpTimer()
timer = myTimer
}
func refreshData() {
//refresh data
print("refresh data")
}
}
I thought it might be possible using the scheduelTimer that takes a selector instead of a block but that would require using a #objc before the func refreshData() which seems clunky since I am using an Objective C feature in Swift.
Is there a nice way to achieve this?
Many Thanks,
Code
Conceptually, you want to decouple the implementation. So instead of having to pass Timer to the view model, you pass some other "control" object, which guarantees to perform the operation (of calling back after a delay)
If that doesn't shout protocol, I don't know what does...
typealias Ticker = () -> Void
protocol Refresher {
var isRunning: Bool { get }
func register(_ ticker: #escaping Ticker)
func start();
func stop();
}
So, pretty basic concept. It can start, stop and an observer can register itself to it and be notified when a "tick" occurs. The observer doesn't care "how" it works, so long as it guarantees to perform the specified operation.
A Timer based implementation then might look something like...
class TimerRefresher: Refresher {
private var timer: Timer? = nil
private var ticker: Ticker? = nil
var isRunning: Bool = false
func register(_ ticker: #escaping Ticker) {
self.ticker = ticker
guard timer == nil else {
return
}
}
func start() {
guard ticker != nil else {
return
}
stop()
isRunning = true
timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true, block: { (timer) in
self.tick()
})
}
func stop() {
guard let timer = timer else {
return
}
isRunning = false
timer.invalidate()
self.timer = nil
}
private func tick() {
guard let ticker = ticker else {
stop()
return
}
ticker()
}
}
This provides you the entry point for mocking the dependency injection, by replacing the implementation of the Refresher with one you can control manually (or use a different "delaying" action, depending on your needs)
This is just a conceptual example, your implementation/needs may differ and lead you to a slightly different design, but the idea remains the same, decouple the physical implementation in some way.
An alternative would require you to rethink your design, and instead of the view model performing it's own refresh, the view/controller would take over that responsibility instead. Since that's a significant design decision, you're really only the person who can make that decision, but it's another idea
If I understand you correctly, you want the model to refresh every 30 seconds when running in the app, but faster for test. If so, don't inject the Timer. Inject the refresh frequency.
class ViewModel: NSObject {
// We need something to observe and confirm that the data is fresh
#objc dynamic var lastRefreshed: Date?
private var timer: Timer!
// The default frequency is 30 seconds but users can adjust that
// The unit test uses it to inject dependency
init(refreshFrequency: TimeInterval = 30) {
super.init()
timer = Timer.scheduledTimer(timeInterval: refreshFrequency, target: self, selector: #selector(refreshData), userInfo: nil, repeats: true)
}
#objc func refreshData() {
lastRefreshed = Date()
print("refreshed on: \(lastRefreshed!)")
}
}
And your unit test:
func testModel() {
let startTime = Date()
let model = ViewModel(refreshFrequency: 5)
// Test first refresh: must be within 5 - 6 seconds from startTime
keyValueObservingExpectation(for: model, keyPath: #keyPath(ViewModel.lastRefreshed)) { (_, _) -> Bool in
if let duration = model.lastRefreshed?.timeIntervalSince(startTime), 5...6 ~= duration {
return true
} else {
return false
}
}
// Test second refresh: must be within 10 - 12 seconds from startTime
keyValueObservingExpectation(for: model, keyPath: #keyPath(ViewModel.lastRefreshed)) { (_, _) -> Bool in
if let duration = model.lastRefreshed?.timeIntervalSince(startTime), 10...12 ~= duration {
return true
} else {
return false
}
}
// Wait 12 seconds for both expectations to be fulfilled
waitForExpectations(timeout: 12, handler: nil)
}
Timer is not exact: it does not fire exactly every 5 seconds like you asked. Apple say Timer is accurate to about 50 - 100ms. Hence we cannot expect that the first refresh will happen 5 seconds from now. We must allow for some tolerances. The further out you go, the bigger this tolerance have to become.

How implement optimized multiple timer in swift?

I just wonder what is the best implementation of memory optimized versatile multi Timers in swift.
The timers which are concurrent and have weak reference with Dispatch?
I've tried to implement two timers in one view controller and I got an error.
one of my timer was like this:
func startOnPlayingTimer() {
let queue = DispatchQueue(label: "com.app.timer")
onPlayTimer = DispatchSource.makeTimerSource(queue: queue)
onPlayTimer!.scheduleRepeating(deadline: .now(), interval: .seconds(4))
onPlayTimer!.setEventHandler { [weak self] in
print("onPlayTimer has triggered")
}
onPlayTimer!.resume()
}
another one was:
carouselTimer = Timer.scheduledTimer(timeInterval: 3, target: self,selector: #selector(scrollCarousel), userInfo: nil, repeats: true)
I dont think there is need for multiple timer for any application.
If from before hand you know which methods you are going to fire, Keep boolean for each method you need to fire and also an Int to save the occurrence of the method. You can invoke the timer once, with a method that checks for the required boolean and its respective method.
A pseudo code referencing the above logic is below :
class ViewController: UIViewController {
var myTimer : Timer!
var methodOneBool : Bool!
var methodTwoBool : Bool!
var mainTimerOn : Bool!
var mainTimerLoop : Int!
var methodOneInvocation : Int!
var methodTwoInvocation : Int!
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure(){
methodOneBool = false
methodTwoBool = false
methodOneInvocation = 5 // every 5 seconds
methodTwoInvocation = 3 //every 3 seconds
mainTimerOn = true // for disable and enable timer
mainTimerLoop = 0 // count for timer main
}
func invokeTimer(){
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(checkTimerMethod), userInfo: nil, repeats: true)
}
func checkTimerMethod(){
if(mainTimerOn){
if(mainTimerLoop % methodOneInvocation == 0 && methodOneBool){
// perform first method
// will only get inside this when
// methodOneBool = true and every methodOneInvocation seconds
}
if(mainTimerLoop % methodTwoInvocation == 0 && methodTwoBool){
// perform second method
// will only get inside this when
// methodTwoBool = true and every methodTwoInvocation seconds
}
mainTimerLoop = mainTimerLoop + 1
}
}
}
I hope this clears up the problem, also if I didnt understand your requirement please comment below, so that I can edit the answer accordingly

NSTimer not firing propery

I have set up a timer class to firer when the viewDidLoad(). I want to have a timer on multiple view controllers thoughout the app. If you have a better solution to a accurate timer on multiple views please suggest.
Viewcontroller -> One of the views that needs a timer
override func viewDidLoad() {
super.viewDidLoad()
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired()),//<- Error Code
userInfo: nil,
repeats: true)
}
The Error Code: Use of instance member 'timerFired' on type 'TestTimer',did you mean to use a value of type 'TestTimer' instead?
Timer Class -> Checks start date compared to current date/time for a accurate timer
class TestTimer: NSTimer {
var timer = NSTimer()
// Converter changes String into NSDate
var startDate = converter("Tue, 26 Apr 2016 09:01:00 MDT")
// Function to be fired
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
print(difference)
}
}
The error you're getting is pretty obscure. What it's trying to tell you is you should remove the () from the end of your timerFired in the #selector.
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired),
userInfo: nil,
repeats: true)
However, this isn't going to make your code how you want it to work – as self in the timer declaration refers to the view controller, not the timer. I would recommend you create a wrapper class for NSTimer, along with a delegate pattern in order to achieve what you want.
You should note that the documentation states that you shouldn't attempt to subclass NSTimer, so you could do something like this instead:
// the protocol that defines the timerDidFire callback method
protocol TimerDelegate:class {
func timerDidFire(cumulativeTime:NSTimeInterval)
}
// your timer wrapper class
class TimerWrapper {
// the underlying timer object
weak private var _timer:NSTimer?
// the start date of when the timer first started
private var _startDate = NSDate()
// the delegate used to implement the timerDidFire callback method
weak var delegate:TimerDelegate?
// start the timer with a given firing interval – which could be a property
func startTimer(interval:NSTimeInterval) {
// if timer already exists, make sure to stop it before starting another one
if _timer != nil {
stopTimer()
}
// reset start date and start new timer
_startDate = NSDate()
_timer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: self,
selector: #selector(timerDidFire),
userInfo: nil, repeats: true)
}
// invalidate & deallocate the timer,
// make sure to call this when you're done with the timer
func stopTimer() {
_timer?.invalidate()
_timer = nil
}
// make sure to stop the timer when the wrapper gets deallocated
deinit {
stopTimer()
}
// called when the timer fires
#objc func timerDidFire() {
// get the change in time, from when the timer first fired to now
let deltaTime = NSDate().timeIntervalSinceDate(_startDate)
// do something with delta time
// invoke the callback method
delegate?.timerDidFire(deltaTime)
}
}
You can then use it like this:
// your view controller class – make sure it conforms to the TimerDelegate
class ViewController: UIViewController, TimerDelegate {
// an instance of the timer wrapper class
let timer = TimerWrapper()
override func viewDidLoad() {
super.viewDidLoad()
// set the timer delegate and start the timer – delegate should be set in viewDidLoad,
// timer can be started whenever you need it to be started.
timer.delegate = self
timer.startTimer(1)
}
func timerDidFire(cumulativeTime: NSTimeInterval) {
// do something with the total time
let dateComponentsFormatter = NSDateComponentsFormatter()
let text = dateComponentsFormatter.stringFromTimeInterval(cumulativeTime)
label.text = text
}
}
As far as the appropriateness of using an NSTimer here goes, as you're only using a time interval of 1 second, an NSTimer is suitable. By taking the time interval over the total timer duration, you can average out any small firing inaccuracies.
This is how a timer is initialized
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0 , target: self, selector: #selector(TestTimer.timerFired()), userInfo: nil, repeats: true)

How to continue a function across ViewControllers using Swift?

I have a NSTimer() that starts upon a button click. If I want the timer to be displayed on a label, I can do that within the same view controller, but I would like the timer to continue and the values (1...2...3) to be displayed on the next view controller. I have a label that displays the time variable on the next view controller but it only displays the value of the time variable when the button is pressed and does not continue. The time variable is passed but the function that runs it is not. How can I go about doing that?
var timer = NSTimer()
var time = 0
inside viewDidLoad :
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("timerFunc"), userInfo: nil, repeats: true)
func timerFunc() {
time++
//time label displays the time (1..2...3 etc)
timeLabel.text = String(time)
}
SecondSceneViewController has a time variable that is passed from this view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var secondScene = segue.destinationViewController as!
SecondViewController
secondScene.time = time
}
When I go to the secondViewController, the value inside the time variable is whatever the time variable was when the button was pressed and it does not continue running. How would I go about passing the timer to the next view controller to display the values?
IMO you should extract the code for the timer into a Singleton and then access it from both ViewControllers
Here's a simple one to get you started:
class TimerManager {
var realSharedInstance: TimerManager?
var sharedInstance: TimerManager {
get{
if let realSharedInstance = realSharedInstance {
return realSharedInstance
}
else{
realSharedInstance = TimerManager()
return realSharedInstance!
}
}
}
var timer: NSTimer
init() {
timer = NSTimer()
}
func rest() {
timer = NSTimer()
}
}
I would create a singleton that will handle the timer event.
let myTimerNotificationNameKey = "timerKey"
let myTimerNotificationUserInfoTimeKey = "timerUserInfoKey"
class myTimer {
static let sharedInstance = myTimer()
var timer: NSTimer
var time = 0
func startTimer() {
time = 0
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timerFunc:", userInfo: nil, repeats: true)
}
func stopTimer() {
timer.invalidate()
timer = nil
}
func timerFunc(timer: NSTimer) {
NSNotificationCenter.defaultCenter().postNotificationName(myTimerNotificationNameKey,object: nil, userInfo: {myTimerNotificationUserInfoTimeKey: time++})
}
}
On the first view controller you call myTimer.sharedInstance.startTimer() function that will start the timer. But first remember to listen to the notifications. The notification will receive a user info dictionary with the time count. Do the same thing on the second view controller.
Now you have a count that you can listen across multiple view controllers. Remember to stop the timer once you stop needing it.
Note: I haven't compiled this code.
If you keep these two variables
var timer = NSTimer()
var time = 0
outside the class they become global and you can access them from another view controller.
like this example:
import UIKit
import CoreData
var fromJSON = true
// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
class CoreDataController: UIViewController {
Another option would be working with a notification center:
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "batteryLevelChanged:",
name: UIDeviceBatteryLevelDidChangeNotification,
object: nil)
#objc func batteryLevelChanged(notification: NSNotification){
//do stuff
}

If i add this function on the code, NSTimer stops, why?

There are 2 Arrays. First one contains Strings that i want to show on UILabel. Second one contains their waiting durations on UILabel.
let items = ["stone","spoon","brush","ball","car"]
let durations = [3,4,1,3,2]
And two variables for specifying which one is on the go.
var currentItem = 0
var currentDuration = 0
This one is the timer system:
var timer = NSTimer()
var seconds = 0
func addSeconds () {seconds++}
func setup () {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "addSeconds", userInfo: nil, repeats: true)
}
Finally, that's the loop. Answer of: Which Array item stays how many seconds on the UILabel question.
func flow () {
while seconds <= durations[currentDuration] {
myScreen.text = items[currentItem]
if seconds == durations[currentDuration]{
seconds == 0
currentItem++
currentDuration++
}
}
Label and Button:
#IBOutlet weak var myScreen: UILabel!
#IBAction func startButton(sender: UIButton) {
setup()
}
}
If i change this:
func addSeconds () {seconds++}
To that:
func addSeconds () {seconds++ ; flow () }
For setting the loop, nothing happens. Even NSTimer, it stops at 1st second.
Because your flow method has a while loop that never exits and blocks the main thread, so the timer can never fire.
Don't use a while loop. Us the method triggered by the timer to update the UI.
So:
func addSeconds () {
seconds++
myScreen.text = items[currentItem]
if seconds == durations[currentDuration] {
seconds == 0
currentItem++
currentDuration++
}
}

Resources