Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protocol extensions cannot fulfill CLLocationManagerDelegate conformance?

I'm trying to implement the CLLocationManagerDelegate protocol requirements via a protocol extension, but the location manager doesn't see it in the protocol extension and fails. However, it works with the same code when moved into the class.

Here's what I'm doing:

class ViewController: UIViewController, MyLocationProtocol {
    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        locationManager.distanceFilter = 1000.0
        locationManager.delegate = self

        // Below crashes when implementation in protocol extension
        locationManager.requestLocation()
    }

}

protocol MyLocationProtocol: CLLocationManagerDelegate {
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
    func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
}

extension MyLocationProtocol /*where Self: UIViewControll*/ { // Tried using where clause but still no go :(

    // Not being triggered by CLLocationManagerDelegate! :(
    // Move to ViewController class and error goes away
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print("MyLocationProtocol: locationManager: didUpdateLocations")
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("MyLocationProtocol: locationManager: didFailWithError")
    }

}

Notice in extension MyLocationProtocol I'm putting the didUpdateLocations and didFailWithError implementations there. They never get trigger and actually crash saying: 'Delegate must respond to locationManager:didUpdateLocations:'. If I move the same didUpdateLocations and didFailWithError code to ViewController, everything works as expected.

Is there something I'm missing on why this is not working through protocol extensions? The class is recognized as comforming to CLLocationManagerDelegate, otherwise it would fail at locationManager.delegate = self. Any ideas on how to make this work or is there a bug somewhere?

like image 806
TruMan1 Avatar asked Feb 20 '16 15:02

TruMan1


1 Answers

protocol extension is pure Swift staff. The rules for dispatch for protocol extensions are:

IF the inferred type of a variable is the protocol

  • AND the method is defined in the original protocol THEN the runtime type’s implementation is called, irrespective of whether there is a default implementation in the extension.
  • AND the method is NOT defined in the original protocol THEN the default implementation is called.

IF the inferred type of the variable is the type THEN the type’s implementation is called.

Taking all this in account ...

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

import Foundation
import MapKit


class Lm: NSObject, CLLocationManagerDelegate {
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [AnyObject]) {
        print("\(locations)")
    }
}


class C:Lm {
    let lm = CLLocationManager()
    override init() {
        super.init()
        lm.delegate = self
        lm.startUpdatingLocation()
    }

}
let c = C()
/*
2016-02-22 08:41:56.506 Untitled Page 10[32000:11547708] ### Failed to load Addressbook class CNContactNameFormatter
[<+48.71408491,+21.20868516> +/- 65.00m (speed -1.00 mps / course -1.00) @ 22/02/16 08 h 41 min 57 s Central European Standard Time]
[<+48.71408491,+21.20868516> +/- 65.00m (speed -1.00 mps / course -1.00) @ 22/02/16 08 h 41 min 57 s Central European Standard Time]
[<+48.71415732,+21.20859246> +/- 65.00m (speed -1.00 mps / course -1.00) @ 22/02/16 08 h 41 min 57 s Central European Standard Time]
....
*/

other option is to do something like ...

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

import Foundation
import MapKit

class MyLocationManager: CLLocationManager, CLLocationManagerDelegate {
    override init() {
        super.init()
        delegate = self
    }
}
extension MyLocationManager {
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [AnyObject]) {
        print("\(locations)")
    }
}
class C {
    let lm = MyLocationManager()
    init() {
        lm.startUpdatingLocation()
    }
}

if all types are known at compile time and there is no 'optional' method in protocol, it works 'as expected'

protocol P { func foo() }
extension P { func foo() { print("p") } }
protocol P1: P {}
extension P1 { func foo() { print("p1") } }
class C: P {}
class C1: P1 {}
let c = C()
let c1 = C1()
let p:P = C()
let p1:P = C1()

c.foo()  // p
c1.foo() // p1
p.foo()  // p
p1.foo() // p1

for further reading see this and this

like image 66
user3441734 Avatar answered Nov 20 '22 18:11

user3441734