Reputation: 63
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
Reputation: 586
Mistakes you have done here are
You are passing target as self that means your selector should be in MapVC
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
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:
- (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
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
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
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