How to Call a Selector-Based Timer Method on a Swift Struct

iOS: How to call a function sequentially after different (defined) amount of time?

There are multiple things wrong here:

  1. Selector(executeCustomScheduler(nextTimeIndex)) creates a selector based on the return value of executing executeCustomScheduler(nextTimeIndex) one time
  2. withObject: self - why would you pass self?
  3. why are you using performSelector at all?
  4. executionTimes is not in milliseconds but in seconds, or at least the method expects them to be
  5. final problem: your argument is a primitive Int which gets really troublesome when passing it around in the performSelector

Solutions:

  1. Selector("executeCustomScheduler:") is the correct selector
  2. nextTimeIndex is the correct argument to pass along
  3. it would probably be better to use something that has a bit more type security, anything with selectors is wide open to a ton of problems regarding renaming e.g.
  4. divide the values by 1000 before passing them into the performSelector
  5. I do not really have a good solution: the easiest and probably worst one: use a String instead of an Int :/

Better solution:
Use dispatch_after, get rid of those pesky selectors and be typesafe all the way through:

class Obj {

let executionTimes = [0, 1500, 3500, 4700]

func executeCustomScheduler(timeIndex: Int) {
NSLog("Hello")

let time = executionTimes[timeIndex]
let nextTimeIndex = (timeIndex + 1) % executionTimes.count

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(time) / 1000 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
self.executeCustomScheduler(nextTimeIndex)
}
}
}

let obj = Obj()
obj.executeCustomScheduler(0)

How do I pass params on timer selector

For sending the data with Timer you can use the userInfo parameter for pass the data.

Here is the sample by which you can get call of selector method and by that you can pass your location coordinate to it.

Timer.scheduledTimer(timeInterval: 0.5, target: self, selector:#selector(iGotCall(sender:)), userInfo: ["Name": "i am iOS guy"], repeats:true)

For handling that userInfo you need to go according to below.

func iGotCall(sender: Timer) {
print((sender.userInfo)!)
}

for your case make sure your didUpdateLocations is called frequently.

@selector() in Swift?

Swift itself doesn't use selectors — several design patterns that in Objective-C make use of selectors work differently in Swift. (For example, use optional chaining on protocol types or is/as tests instead of respondsToSelector:, and use closures wherever you can instead of performSelector: for better type/memory safety.)

But there are still a number of important ObjC-based APIs that use selectors, including timers and the target/action pattern. Swift provides the Selector type for working with these. (Swift automatically uses this in place of ObjC's SEL type.)

In Swift 2.2 (Xcode 7.3) and later (including Swift 3 / Xcode 8 and Swift 4 / Xcode 9):

You can construct a Selector from a Swift function type using the #selector expression.

let timer = Timer(timeInterval: 1, target: object,
selector: #selector(MyClass.test),
userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
with: button, with: otherButton)

The great thing about this approach? A function reference is checked by the Swift compiler, so you can use the #selector expression only with class/method pairs that actually exist and are eligible for use as selectors (see "Selector availability" below). You're also free to make your function reference only as specific as you need, as per the Swift 2.2+ rules for function-type naming.

(This is actually an improvement over ObjC's @selector() directive, because the compiler's -Wundeclared-selector check verifies only that the named selector exists. The Swift function reference you pass to #selector checks existence, membership in a class, and type signature.)

There are a couple of extra caveats for the function references you pass to the #selector expression:

  • Multiple functions with the same base name can be differentiated by their parameter labels using the aforementioned syntax for function references (e.g. insertSubview(_:at:) vs insertSubview(_:aboveSubview:)). But if a function has no parameters, the only way to disambiguate it is to use an as cast with the function's type signature (e.g. foo as () -> () vs foo(_:)).
  • There's a special syntax for property getter/setter pairs in Swift 3.0+. For example, given a var foo: Int, you can use #selector(getter: MyClass.foo) or #selector(setter: MyClass.foo).

General notes:

Cases where #selector doesn't work, and naming: Sometimes you don't have a function reference to make a selector with (for example, with methods dynamically registered in the ObjC runtime). In that case, you can construct a Selector from a string: e.g. Selector("dynamicMethod:") — though you lose the compiler's validity checking. When you do that, you need to follow ObjC naming rules, including colons (:) for each parameter.

Selector availability: The method referenced by the selector must be exposed to the ObjC runtime. In Swift 4, every method exposed to ObjC must have its declaration prefaced with the @objc attribute. (In previous versions you got that attribute for free in some cases, but now you have to explicitly declare it.)

Remember that private symbols aren't exposed to the runtime, too — your method needs to have at least internal visibility.

Key paths: These are related to but not quite the same as selectors. There's a special syntax for these in Swift 3, too: e.g. chris.valueForKeyPath(#keyPath(Person.friends.firstName)). See SE-0062 for details. And even more KeyPath stuff in Swift 4, so make sure you're using the right KeyPath-based API instead of selectors if appropriate.

You can read more about selectors under Interacting with Objective-C APIs in Using Swift with Cocoa and Objective-C.

Note: Before Swift 2.2, Selector conformed to StringLiteralConvertible, so you might find old code where bare strings are passed to APIs that take selectors. You'll want to run "Convert to Current Swift Syntax" in Xcode to get those using #selector.

I am trying to make a timer in Swift with time interval

You can do this with a Timer or an SKAction with delays and repeats.

To use a timer, call the scheduledTimer class method and specify a method to call when it fires.

timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GameScene.updateCounter), userInfo: nil, repeats: true)

This method should update some counter variable, display the current time to the user, and check if the game over criteria are met.

func updateCounter() {
counter -= 1
timerLabel.text = "\(counter)"
if counter == 0 { gameOver() }
}

In the gameOver function you can stop the timer by calling

timer.invalidate()

As the complexity of your game grows, you may wish to refactor this differently.

Another method is to use an SKAction.

let waitAction = SKAction.wait(forDuration: 1)
let fireAction = SKAction.run {
counter -= 1
timerLabel.text = "\(counter)"
if counter == 0 { gameOver() }
}
let actionSequence = SKAction.sequence([waitAction, fireAction])
let repeatAction = SKAction.repeatForever(actionSequence)
self.run(repeatAction, withKey: "timer")

In the gameOver function you can stop the action by calling

self.removeAction(forKey: "timer")

Hope that helps! Good luck with your game.

Using a class inside a struct is giving an error: partial application of 'mutating' method is not allowed

SwiftUI Views should not have mutating properties/functions. Instead, they should use property wrappers like @State and @StateObject for state.

Besides the mutating function, you're fighting the principals of SwiftUI a bit. For example, you should never try to keep a reference to a View and call a function on it. Views are transient in SwiftUI and should not be expected to exist again if you need to call back to them. Also, SwiftUI tends to go hand-in-hand with Combine, which would be a good fit for implementing in your Timer code.

This might be a reasonable refactor:

import SwiftUI
import Combine

// Class with the timer logic
class TimerLogic : ObservableObject {
@Published var timerEvent : Timer.TimerPublisher.Output?
private var cancellable: AnyCancellable?

func startTimer() {
cancellable = Timer.publish(every: 3.0, on: RunLoop.main, in: .default)
.autoconnect()
.sink { event in
self.timerEvent = event
}
}

func stopTimer() {
cancellable?.cancel()
}
}

struct ContentView: View {

//Timer to send information to phone
@StateObject var timerLogic = TimerLogic()

var body: some View {
Button(action: timerLogic.startTimer) {
Image(systemName: "location")
}
.onChange(of: timerLogic.timerEvent) { _ in
timerTicked()
}
}

// Function to run with each timer tick, this can be any action
func timerTicked() {
print("Timer ticked...")
//...
}
}


Related Topics



Leave a reply



Submit