Search code examples
swiftnstimerunrecognized-selector

Timer.scheduledTimer throws "unrecognized selector sent to instance"


I am scheduling a timer that I want to push notifications. The caveat is that my #selector is a func of a singleton as outlined in the code below. However, when the timer is signaled, and the #selector(NotificationManager.instance.pushNotification(timer:)) is called, I get the exception dump outlined below. When I take that same func and place it in the view controller, all is right in the world and no issues. Any ideas?

2017-12-09 20:33:47.439754-0500 Accumoo[3404:2488862] -[MyApp.MainViewController pushNotificationWithTimer:]: unrecognized selector sent to instance 0x107077800
2017-12-09 20:33:47.440181-0500 Accumoo[3404:2488862] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyApp.MainViewController pushNotificationWithTimer:]: unrecognized selector sent to instance 0x107077800'
*** First throw call stack:
(0x184c89d04 0x183ed8528 0x184c971c8 0x18e47f110 0x184c8f6b0 0x184b7501c 0x18567bd24 0x184c3292c 0x184c32650 0x184c31e50 0x184c2fa38 0x184b4ffb8 0x1869e7f84 0x18e1242e8 0x104b3f624 0x18467256c)
libc++abi.dylib: terminating with uncaught exception of type NSException 

Here's the code...

In my View Controller...

// Send user a local notification if they have the app running in the background...
let userInfo = ["title" : "Title", "body" : "Body", "timeIntervalSeconds" : Double(0), "repeats" : false] as [String : Any]
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(NotificationManager.instance.pushNotification(timer:)), userInfo: userInfo, repeats: false)

In My NotificationManager Class...

class NotificationManager /*: PushNotificationProtocol*/ {
   static let instance = NotificationManager()

   private init() {
   }

   @objc public func pushNotification(timer:Timer) {
      guard let userInfo = timer.userInfo as? Dictionary<String, Any> else { return }

      let title = (userInfo["title"] as? String).valueOrDefault()
      let body = (userInfo["body"] as? String).valueOrDefault()
      let timeIntervalSeconds = (userInfo["timeIntervalSeconds"] as? Double) ?? 0
      let repeats = (userInfo["repeats"] as? Bool) ?? false

      self.pushNotification(sender: nil, title: title, body: body, timeIntervalSeconds: timeIntervalSeconds, repeats: repeats)
   }

   public func pushNotification(sender:Any?, title:String, body:String) {
      self.pushNotification(sender: sender, title: title, body: body, timeIntervalSeconds: 0, repeats: false)
   }

   public func pushNotification(sender:Any?, title:String, body:String, timeIntervalSeconds:Double, repeats:Bool) {
      // Center
      let center = UNUserNotificationCenter.current()

      // Content...
      let content = UNMutableNotificationContent()
      content.title = title
      content.body = body
      content.sound = UNNotificationSound.default()

      // Trigger
      let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeIntervalSeconds, repeats: repeats)

      // Push it...
      // Does this need to be unique?
      let identifier = "NotificationManagerIdentification"
      let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
      center.add(request, withCompletionHandler: { (error) in
         if let error = error {
            print("Something went wrong: \(error)")
         }
      })
   }
}

** * UPDATE * **

It turns out the call to Timer.scheduleTimer needed to be coded like this (see @maddy's answer):

// Send user a local notification if they have the app running in the background...
let userInfo = ["title" : "Title", "body" : "Body", "timeIntervalSeconds" : Double(10), "repeats" : false] as [String : Any]
Timer.scheduledTimer(timeInterval: 0.5, target: NotificationManager.instance, selector: #selector(NotificationManager.instance.pushNotification(timer:)), userInfo: userInfo, repeats: false)

Solution

  • You are passing the wrong target to your timer.

    Change:

    Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(NotificationManager.instance.pushNotification(timer:)), userInfo: userInfo, repeats: false)
    

    to:

    Timer.scheduledTimer(timeInterval: 0.5, target: NotificationManager.instance, selector: #selector(NotificationManager.instance.pushNotification(timer:)), userInfo: userInfo, repeats: false)
    

    Remember, the target needs to an instance of the class that actually implements the selector to specify. You are passing self which is the view controller.