协议扩展无法满足CLLocationManagerDelegate的一致性?

时间:2016-02-20 15:08:55

标签: ios swift swift2 cllocationmanager protocol-extension

我正在尝试通过协议扩展实现CLLocationManagerDelegate协议要求,但位置管理器在协议扩展中没有看到它并且失败。但是,当它移入类中时,它使用相同的代码。

这就是我正在做的事情:

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")
    }

}

请注意extension MyLocationProtocol我将didUpdateLocationsdidFailWithError实施放在那里。他们永远不会触发并实际崩溃说:'Delegate must respond to locationManager:didUpdateLocations:'。如果我将相同的didUpdateLocationsdidFailWithError代码移至ViewController,则一切都会按预期运行。

为什么这不通过协议扩展工作,我有什么遗漏?该类被识别为符合CLLocationManagerDelegate,否则将在locationManager.delegate = self失败。关于如何使这项工作或某处有错误的任何想法?

3 个答案:

答案 0 :(得分:2)

协议扩展是纯粹的Swift员工。协议扩展的调度规则是:

如果变量的推断类型是协议

  • AND方法在原始协议中定义,然后调用运行时类型的实现,无论扩展名中是否存在默认实现
  • AND方法未在原始协议中定义,然后调用默认实现。

如果变量的推断类型是类型 然后调用类型的实现。

将所有这些考虑在内......

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]
....
*/

其他选择是做某事......

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()
    }
}

如果所有类型在编译时都已知并且协议中没有“可选”方法,则它可以“按预期”工作

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

有关详细信息,请参阅thisthis

答案 1 :(得分:0)

协议扩展是swift 2.0的新功能之一。这允许您自己定义协议的行为。如果已在确认类和协议扩展中实现了方法定义,则在运行时将调用确认类中的方法实现。但是,任选协议实际上是目标c的副产品。这意味着如果要在swift中定义可选协议,则需要在协议声明之前插入@objc属性。当我像你解释的那样试验协议扩展时,当协议不是可选的时,它很有效。但是当协议是可选的时,应用程序就崩溃了。 CLLocationManagerDelegate委托方法声明为可选。我想这可能是原因。

查看控制器1

class ViewController: UIViewController,MyViewControllerProtocol {

var myViewController:NewViewController?

override func viewDidLoad() {
    super.viewDidLoad()

    let sb = UIStoryboard(name: "Main", bundle: nil)
    myViewController = sb.instantiateViewControllerWithIdentifier("newViewController") as? NewViewController
    myViewController?.delegate = self
    myViewController?.view.frame = self.view.bounds
    self.view.addSubview((myViewController?.view)!)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

func newViewControllerLoaded(){
    print("protocol definition called inside class")
}

}

protocol MyViewControllerProtocol: NewViewControllerProtocol {
   func newViewControllerLoaded()
}

extension MyViewControllerProtocol{

func newViewControllerLoaded(){
   print("protocol definition called inside extension")
}

}

查看控制器2

protocol NewViewControllerProtocol {

func newViewControllerLoaded()
}

class NewViewController: UIViewController {

var delegate: NewViewControllerProtocol?

override func viewDidLoad() {
    delegate?.newViewControllerLoaded()
}

}

ViewController

中使用协议实现的输出
protocol definition called inside class
ViewController

中删除协议实现后输出

protocol definition called inside extension

解决问题的一种方法是将扩展应用于您的类,然后协议实现将在您的类中。但这不是协议扩展。

extension ViewController /*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")
}

}

答案 2 :(得分:0)

我发现这种行为有点奇怪。但是对于您当前的问题,您可以使用此解决方案。

基本上,我们的想法是引入一个代理对象,您可以在其中实现处理协议中位置的逻辑,并编写符合该协议的类。然后在ViewController中创建类(代理)的实例,并实现CLLocationManagerDelegate方法,并将回调方法传递给代理对象。这将帮助您从ViewController中分离出位置处理逻辑。您仍然可以在Watch应用程序中独立使用MyLocationManagerDelegateClass。

希望这有帮助。

class ViewController: UIViewController, CLLocationManagerDelegate
{
    let locationManager = CLLocationManager()
    let proxyLocationManagerDelegate = MyLocationManagerDelegateClass()

    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        locationManager.distanceFilter = 1000.0
        // Below crashes when implementation in protocol extension
        locationManager.delegate = self
        locationManager.requestLocation()
    }

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print("ViewController: locationManager: didUpdateLocations")
        proxyLocationManagerDelegate.locationManager(manager, didUpdateLocations: locations)
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("ViewController: locationManager: didFailWithError")
        proxyLocationManagerDelegate.locationManager(manager, didFailWithError: error)
    }
}

class MyLocationManagerDelegateClass : NSObject, MyCLLocationManagerDelegate {

}

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

extension MyCLLocationManagerDelegate /*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")
    }

}