Hazem Mohamed
Hazem Mohamed

Reputation: 63

unrecognized selector sent to instance-Swift

Hello All: i have a function that hit API to download data (LAT,LONG)and it works 100% fine. but the problem is i want to recall this function every 5 second and after i add a timer to do that every time i try to run it run very well and after 5 second i got crash

unrecognized selector sent to instance 0x7ffa4a51cb00
2018-07-20 11:05:31.191467+0200 Interactive Bus[684:6752] *** 
Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '-[Interactive_Bus.MapVC 
downloadBusDataWithUserId:warning:delegate:completed:]: unrecognized 
selector sent to instance 0x7ffa4a51cb00'

i try to clean then build and there is no Code Error i have no idea why this crash happens

this is my Model Class

//  busLocationModel.swift
//  Interactive Bus
import UIKit
import Alamofire
import SwiftyJSON

class busLocationModel {

var route_pins: String?
var busLatitude: Double?
var busLongitude: Double?

@objc func downloadBusData(userId: String,warning: @escaping (String,String,String) -> Void, delegate : MapVC ,completed : @escaping DownloadCompleted) {

    let Parameters = ["parent_id": userId]
    print(#function)
    Alamofire.request(busLocationUrl, method: .get, parameters: Parameters).responseJSON { (response) in

        switch response.result {

        case .failure(let error):

            print(error)

            let alertControllerTitle = "Warning"
            let actionButtonTitle = "Ok"
            let alertMessage = "Some Thing Went Wrong Please Try Agin Later "

            return warning(alertControllerTitle, actionButtonTitle, alertMessage)

        case .success(let Value):

            let json = JSON(Value)
            //print(json)

            let status = json["status"].boolValue

            if status != false {

                for locations in json["data"].arrayValue {

                    let busPins = locations["route_pins"].stringValue
                    let bus_lat = locations["bus_lat"].doubleValue
                    let bus_long = locations["bus_long"].doubleValue

                    delegate.busPins = busPins
                    delegate.currentBusLate = bus_lat
                    delegate.currentBusLong = bus_long

                    print(delegate.busPins ?? "HH")
                    print("the bus lat is \(bus_lat)")
                    print("the bus long is \(bus_long)")
                }

            }
        }
        completed()
    }
}
}

and my Const is :

typealias DownloadCompleted = () -> ()

and My MapVC is :

//
import UIKit
import MapKit
import CoreLocation

class MapVC: UIViewController {

@IBOutlet weak private var busMapView: MKMapView!

var locationManager = CLLocationManager()
//var locationManager: CLLocationManager!
let authorizationStatus = CLLocationManager.authorizationStatus()
fileprivate let regionRadius: Double = 1000 //Meter's From UP,Right,Down and Left
fileprivate var busInfoObject: busLocationModel!

var busPins: String!
var currentBusLate: CLLocationDegrees?
var currentBusLong: CLLocationDegrees?

var callFuncTimer: Timer!



override func viewDidLoad() {
    super.viewDidLoad()

    locationManager.delegate = self
    busMapView.delegate = self
    busInfoObject = busLocationModel()

    let myActivity = CreatActivityIndicator()


    busInfoObject.downloadBusData(userId: "366", warning: DisplayAlertMessage, delegate: self) {

        self.drawLine()
        self.RemoveActivityIndicator(ActivityIndicator: myActivity)

        guard let latitude = self.currentBusLate else { return }
        guard let longitude = self.currentBusLong else { return }

        let Location = CLLocation(latitude: latitude, longitude: longitude)

        self.centerMapOnBusLocation(location: Location)

        self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)

    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(true)

    configureLocationServices()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(true)

    callFuncTimer.invalidate()
}

@IBAction func centerMapBtnPressed(_ sender: Any) {

    if authorizationStatus == .authorizedAlways {

        guard let latitude = self.currentBusLate else { return }
        guard let longitude = self.currentBusLong else { return }

        let Location = CLLocation(latitude: latitude, longitude: longitude)

        centerMapOnBusLocation(location: Location)
    }
}
}
extension MapVC: MKMapViewDelegate {

fileprivate func centerMapOnBusLocation(location: CLLocation) {

    //guard let currtntLocationCoordinate = locationManager.location?.coordinate else { return }
    let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate , regionRadius * 2.0, regionRadius * 2.0)
    busMapView.setRegion(coordinateRegion, animated: true)
}


func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {

    if (overlay is MKPolyline) {
        let pr = MKPolylineRenderer(overlay: overlay)
        pr.strokeColor = UIColor.blue
        pr.lineWidth = 5
        return pr
    }
    return MKPolylineRenderer()
}
}

extension MapVC {

func drawLine() {

    let coordinates = busPins.components(separatedBy: "#").dropFirst().map { (pin) -> CLLocationCoordinate2D in
        let latLng = pin.components(separatedBy: ",").map{ CLLocationDegrees($0)! }
        return CLLocationCoordinate2D(latitude: latLng[0], longitude: latLng[1])
    }

    let polyLine = MKPolyline(coordinates: coordinates , count: coordinates.count)
    self.busMapView.add(polyLine)
}

}

extension MapVC: CLLocationManagerDelegate {

fileprivate func configureLocationServices() {

    if authorizationStatus == .notDetermined {
        locationManager.requestAlwaysAuthorization()
    } else {

        return
    }
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

    guard let latitude = self.currentBusLate else { return }
    guard let longitude = self.currentBusLong else { return }

    let Location = CLLocation(latitude: latitude, longitude: longitude)

    centerMapOnBusLocation(location: Location)
}
}

and this is DisplayAlertMessage:

    func DisplayAlertMessage(alertControllerTitle: String , actionButtonTitle: String , alertMessage: String) -> Void{


    let alertcontroller = UIAlertController(title: alertControllerTitle, message: alertMessage , preferredStyle: .alert)

    let okaction = UIAlertAction(title: actionButtonTitle, style: .default, handler: nil)

    alertcontroller.addAction(okaction)
    self.present(alertcontroller, animated: true, completion: nil)
}

I can see no code Error i do the @OBJC the selector syntax is right but i still get the Error (unrecognized selector sent to instance) can you help me with that??

Upvotes: 1

Views: 2072

Answers (5)

Manish Malviya
Manish Malviya

Reputation: 586

Mistakes you have done here are

  1. You are passing target as self that means your selector should be in MapVC

  2. The selector you are passing is incorrect. According to Apple docs your selector should have signature

    -(void)timerFireMethod:(NSTimer *)timer

Refer this https://developer.apple.com/documentation/foundation/timer/1412416-scheduledtimer

So to make it work do this:

func timerFireMethod(_ timer: Timer?) {

   self.busInfoObject.downloadBusData(your parameters)

}

// Register timer

self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.timerFireMethod), userInfo: nil, repeats: true)

To make it easy you can use this method

self.callFuncTimer = Timer(timeInterval: 5, repeats: true, block: { (timer) in
            print("timer")
        })

Upvotes: 1

OOPer
OOPer

Reputation: 47876

In target-action pattern, the target object needs to implement the action method.

But in your code:

self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)

You use self as target, which is an instance of MapVC, that does not implement the method downloadBusData(userId:warning:delegate:completed:).

When you specify #selector(someInstance.methodName(...)) for the action method, you need to pass someInstance to the target object. In your case someInstance is self.busInfoObject.

Which means the line creating a Timer should become like this:

self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self.busInfoObject, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)

But this does not work.


I was stupid enough that I have almost forgotten to tell you another important thing in target-action pattern.

Which is,

The signature of the action method is definitely fixed according to the target.

When using Timer, the signature needs to be the same as this:

class func scheduledTimer(timeInterval: TimeInterval, target: Any, selector: Selector, userInfo: Any?, repeats: Bool) -> Timer

- (void)timerFireMethod:(NSTimer *)timer

The notation is in Objective-C format, but the action method for Timer needs to have one and only one argument of type Timer (it's NSTimer in Objective-C.)

So, you may need to define a method matches the signature in your MapVC:

func timerFired(_ timer: Timer) {
    self.busInfoObject.downloadBusData(userId: ...,
                                       warning: {_, _, _ in
                                           ...
                                       }, 
                                       delegate: self,
                                       completed: {
                                           ...
                                       })
}

And change the line setting the timer to:

self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.timerFired(_:)), userInfo: nil, repeats: true)

Sorry, for showing you an incomplete answer, please try with filling ... in my updated code.

Upvotes: 1

Abhishek Jadhav
Abhishek Jadhav

Reputation: 706

When you call function for every 5 seconds replace with this line and Please follow the convention of Swift. Write First letter capital for class name.

self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(BusLocationModel.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)

Upvotes: 0

ManuRaphy
ManuRaphy

Reputation: 391

Your invokation of the method missing some parameters

func downloadBusData(userId: String,warning: @escaping (String,String,String) -> Void, delegate : MapVC ,completed : @escaping DownloadCompleted)

as per your declaration it should have userId,warning,delegate and the completion handler

Upvotes: 0

Rakesh Patel
Rakesh Patel

Reputation: 1701

Your settled target for timer is wrong. You should set function for timer and call.

func timerUpdates(_ timer : Timer)
{ 
  self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)
}
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target:#selector(self.timerUpdates:) self, selector: #selector(), userInfo: nil, repeats: true)

Upvotes: 0

Related Questions