4

This is interesting: it appears you can't call a selector message on a swift struct! What I mean is this: I've got a struct and want to implement a simple Timer on it, using the variation of Timer.scheduledTimer that takes a selector: argument. The compiler tells me that the selector method being called needs an objc attribute to make the method visible to an Objective-C calling sequence, but that's not supported (yet another helpful compiler message! ;-) for non-class objects.

I thought using a struct was a better way to go in my particular case, but I guess I'm going to have to back-port it to a class unless somebody has a better idea...

hkatz
  • 951
  • 11
  • 21
  • 2
    Thats really imposible. How would you even pass the struct as a target? It would get copied... Timers are made for reference types. If absolutely required (probably isnt), you can use CFTimer. – Sulthan May 23 '17 at 22:03
  • I'm not passing a struct. I'm trying to call a selector-style message @on@ a struct. Sorry if that wasn't clear. (And it might well not be. This was a tricky problem to describe, and I'm not sure my command of technical vocabulary was entirely up to the task. One can only do what one can do, however...) – hkatz May 23 '17 at 22:33
  • That is interesting. – Norman May 24 '17 at 01:16
  • To call a selector, you need to pass the target. Therefore you would have to pass the struct too. But thats only one if the reasons why this could never work. – Sulthan May 24 '17 at 04:30
  • you can use `Timer.scheduledTimer(withTimeInterval:, repeats:,block:)` witch has a block or GCD Timer: `DispatchSource.makeTimerSource ` to avoid use `selector` – Wilson XJ May 24 '17 at 05:10
  • @Sulthan You're right: you are passing the struct (self) as the target. But that doesn't mean it wouldn't work; it would just be inefficient, no? In my case the struct is quite small. – hkatz May 24 '17 at 13:15
  • @Wilson XJ I looked at the variation that uses block: -- that's a nicer api IMHO, but it's limited to iOS 10.0 or later, and I didn't want that restriction. – hkatz May 24 '17 at 13:24
  • 2
    @hkatz Nope, it just wouldn't work. It's just impossible to use selectors with value types. Selectors are inherently tied to reference types, or, more specifically, `NSObject` descendants. – Sulthan May 24 '17 at 13:59
  • @Sulthan Just reread this thread. You are indeed correct. Thx. – hkatz May 24 '17 at 16:16

1 Answers1

4

I know this is an old question, but as others have pointed out in the comments this is not possible, directly anyways.

You can achieve creation of Swift Timers within structs by isolating the timer logic into a class within the struct. The overall plan is as follows.

  1. Isolate Timer logic into a class within the struct.
  2. Pass the containing struct (or some other class instance within the struct) as a way to communicating back timer ticks.
  3. Instantiate the logic class, and store a reference to it until needed.

Please see the code example below.

struct MyStruct{
    
    var timerLogic: TimerLogic!

    class TimerLogic{
        var structRef: MyStruct!
        var timer: Timer!

        init(_ structRef: MyStruct){
            self.structRef = structRef;
            self.timer = Timer.scheduledTimer(
                timeInterval: 2.0,
                target: self,
                selector: #selector(timerTicked),
                userInfo: nil,
                repeats: true)
        }

        func stopTimer(){
            self.timer?.invalidate()
            self.structRef = nil
        }

        @objc private func timerTicked(){
            self.structRef.timerTicked()
        }
    }

    func startTimer(){
        self.timerLogic = TimerLogic(self)
    }    

    func timerTicked(){
         print("Timer tick notified")

         // Stop at some point
         // timerLogic.stopTimer()
         // timerLogic = nil
    }
}
  • 1
    thanks for taking the time to follow up, even tho quite a bit of time has passed. that's an interesting answer and has taught me something I didn't know before. – hkatz Nov 16 '20 at 16:54
  • This is a nice solution, with one exception. In `func startTimer()`, Xcode squawks about `self.timerLogic`. This makes it a no-go. – David May 12 '21 at 01:56
  • 1
    `mutating func startTimer() { self.timerLogic = TimerLogic(self) }` This should help – Aftab Ahmed Jul 14 '21 at 01:28