Search code examples
iosxcodeswiftnstimergoogle-maps-sdk-ios

Can NSTimer() run non-stop? (Xcode v 7.0.1, Swift 2.0)


I'm building an iOS app that displays the position of campus shuttles for my school on a map using the Google Maps SDK. I get the positions of the shuttles by parsing an XML file that refreshes every 10 seconds.

I'm using NSTimer() to run viewDidLoad() every 10 seconds so that the position of the buses gets updated on the map.

The issue I'm having is that after 6 or 7 runs of viewDidLoad() the code crashes. This is bad because I'll need my code to continuously refresh in order to update the buses' positions.

Am I using NSTimer() correctly? Can NSTimer() continuously keep running or is there a limit of how long NSTime() can run?

The following is my code:

import UIKit
import GoogleMaps

class ViewController: UIViewController, NSXMLParserDelegate {

    var parser = NSXMLParser()
    var timer  = NSTimer()

    // viewDidLoad()
    override func viewDidLoad() {
        super.viewDidLoad()

        // runs viewDidLoad() every 10 seconds
        timer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "viewDidLoad", userInfo: nil, repeats: true)

        // XML file
        parser = NSXMLParser(contentsOfURL:(NSURL(string:"http://link.to.xml.file.xml"))!)!

        let coord = Coord2()
        parser.delegate = coord
        parser.parse()
        print("coord has a count attribute of \(coord.count)")
        print("coord has \(coord.markers.count) markers")

        // displays the map adjusted to UC Santa Cruz
        let camera = GMSCameraPosition.cameraWithLatitude(37.0000,
        longitude: -122.0600, zoom: 14)
        let mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
        mapView.mapType = kGMSTypeNormal
        mapView.myLocationEnabled = true
        self.view = mapView

        // loops through all the lats and lngs of the buses and produces a marker
        // for them on our Google Maps app
        for marker in coord.markers {
            print("marker id=\(marker.id), lat=\(marker.lati), lng=\(marker.lngi), route=\(marker.route)")

            // displays the buses
            let buses = GMSMarker()
            buses.position = CLLocationCoordinate2DMake(marker.lati, marker.lngi)
            buses.title = marker.route
            if buses.title == "UPPER CAMPUS" {
                buses.icon = UIImage(named: "uppercampus")
            } else if buses.title == "LOOP" {
                buses.icon = UIImage(named: "innerloop")
            }
            buses.snippet = marker.id
            buses.map = mapView
        }
    }

    // didReceiveMemoryWarning()
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

// ParseBase class
// simple base class that is used to consume foundCharacters
// via the parser
class ParserBase : NSObject, NSXMLParserDelegate  {

    var currentElement:String = ""
    var foundCharacters = ""
    weak var parent:ParserBase? = nil

    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        currentElement = elementName
    }

    func parser(parser: NSXMLParser, foundCharacters string: String) {
        self.foundCharacters = string
    }

}

// Coord2 class
// represents a coord2 tag
// it has a count attribute
// and a collection of markers
class Coord2 : ParserBase {

    var count = 0
    var markers = [Marker]()

    // didStartElement()
    override func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {

        print("processing <\(elementName)> tag from Coord")

        if elementName == "coord2" {

            // if we are processing a coord2 tag, we are at the root
            // of XML file, extract the count value and set it
            print(attributeDict["count"])
            if let c = Int(attributeDict["count"]!) {
                self.count = c
            }
        }

        // if we found a marker tag, delegate further responsibility
        // to parsing to a new instance of Marker
        if elementName == "marker" {
            let marker = Marker()
            self.markers.append(marker)

            // push responsibility
            parser.delegate = marker

            // let marker know who we are
            // so that once marker is done XML processing
            // it can return parsing responsibility back
            marker.parent = self
        }
    }
}

// Marker class
class Marker : ParserBase {

    var id = ""
    var lat = ""
    var lng = ""
    var route = ""
    var lati = 0.0
    var lngi = 0.0

    // didEndElement()
    func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {

        print("processing <\(elementName)> tag from Marker")

        // if we finished an item tag, the ParserBase parent
        // would have accumulated the found characters
        // so just assign that to our item variable
        if elementName == "id" {
            self.id = foundCharacters
        }

        // convert the lat to a Double
        else if elementName == "lat" {
            self.lat = foundCharacters
            // cast self.lat as Double
            if let doubleFromlat = Double(self.lat) {
                self.lati = doubleFromlat
            } else { print("foundCharacters for lat does not hold double") }

        }

        // convert the lng to a Double
        else if elementName == "lng" {
            self.lng = foundCharacters
            if let doubleFromlng = Double(self.lng) {
                self.lngi = doubleFromlng
            } else { print("foundCharacters for lng does not hold double") }
        }

        else if elementName == "route" {
            self.route = foundCharacters
        }

        // if we reached the </marker> tag, we do not
        // have anything further to do, so delegate
        // parsing responsibility to parent
        else if elementName == "marker" {
            parser.delegate = self.parent
        }

        // reset found characters
        foundCharacters = ""
    }

}

Solution

  • Calling viewDidLoad method everytime is bad idea.

    viewDidLoad is called exactly once, when the view controller is first loaded into memory. This is where you want to instantiate any instance variables and build any views that live for the entire lifecycle of this view controller. However, the view is usually not yet visible at this point.

    Instead of calling viewDidLoad method everytime create another method and add required code into that method which you want to run in every 10 second as shown below:

    timer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "yourNewMethod", userInfo: nil, repeats: true)
    

    update your timer as shown above.

    And below is the helper method.

    func yourNewMethod() {
        //add your code here which you want to run
    }