2016-02-20 26 views
6

Sto cercando di implementare i requisiti del protocollo CLLocationManagerDelegate tramite un'estensione di protocollo, ma il gestore di posizione non lo vede nell'estensione del protocollo e non riesce. Tuttavia, funziona con lo stesso codice quando viene spostato nella classe.Le estensioni del protocollo non possono soddisfare la conformità CLLocationManagerDelegate?

Ecco quello che sto facendo:

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

} 

del bando nella extension MyLocationProtocol sto mettendo le didUpdateLocations e didFailWithError implementazioni lì. Non ottengono mai il grilletto e in realtà si bloccano dicendo: 'Delegate must respond to locationManager:didUpdateLocations:'. Se cambio lo stesso codice didUpdateLocations e didFailWithError in ViewController, tutto funziona come previsto.

C'è qualcosa che mi manca sul motivo per cui questo non funziona attraverso le estensioni del protocollo? La classe viene riconosciuta come uguale a CLLocationManagerDelegate, altrimenti non riuscirà a locationManager.delegate = self. Qualche idea su come fare questo lavoro o c'è un bug da qualche parte?

+0

da dove proviene la funzione requestLocation()? – user3441734

+0

Una nuova API iOS 9. – TruMan1

risposta

1

estensione del protocollo è puro personale di Swift. Le regole per la consegna di protocollo estensioni sono:

IF del tipo derivato di una variabile è il protocollo

  • E il metodo è definito nel protocollo originale THEN attuazione del tipo runtime è chiamato, indipendentemente se esiste un'implementazione predefinita nell'estensione.
  • E il metodo NON è definito nel protocollo originale ALLORA è stata richiamata l'implementazione predefinita.

IF del tipo derivato della variabile è il tipo THEN attuazione del tipo è chiamato.

Prendendo tutto questo in considerazione ...

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

altra opzione è quella di fare qualcosa di simile ...

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

se tutti i tipi sono noti al momento della compilazione e non c'è ' opzionale 'metodo in protocollo, funziona' come previsto '

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 

per ulteriori letture vedere this e this

+0

Grazie per la spiegazione e gli articoli, ma non riesco ancora a vedere come ciò non sia possibile. Dal momento che sto passando 'self' alla proprietà delegato' CLLocationManagerDelegate', non dovrei 'self', il protocollo a cui è conforme e le estensioni del protocollo sono disponibili? – TruMan1

+0

@ Le funzioni di TruMan1 in CLLocationManagerDelegate sono "facoltative". ciò significa che dovrebbe esistere un'implementazione di default che viene eseguita e non fa nulla. se tale implementazione non esiste, viene eseguita la funzione dall'estensione del protocollo. vedi, che non hai alcun controllo sul tipo di proprietà delegate, è CLLocationManagerDelegate – user3441734

0

L'estensione del protocollo è una delle novità di swift 2.0. Questo ti permette di definire il comportamento sul protocollo stesso. Se è stata implementata la definizione del metodo sia nella classe di conferma che nell'estensione del protocollo, in fase di esecuzione verrà richiamata l'implementazione del metodo nella classe di conferma. Ma il protocollo opzionale è in realtà un sottoprodotto dell'obiettivo c. Ciò significa che se si desidera definire il protocollo opzionale in modo rapido, è necessario inserire l'attributo @objc prima della dichiarazione del protocollo. Quando ho sperimentato l'estensione del protocollo come hai spiegato, funziona bene quando il protocollo non è opzionale. Ma quando il protocollo è opzionale, l'applicazione si è bloccata. I metodi di delegazione CLLocationManagerDelegate sono dichiarati come facoltativi. Immagino che possa essere la ragione.

vista del regolatore 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") 
} 

} 

Vista regolatore 2

protocol NewViewControllerProtocol { 

func newViewControllerLoaded() 
} 

class NewViewController: UIViewController { 

var delegate: NewViewControllerProtocol? 

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

} 

uscita con implementazione del protocollo in ViewController

protocol definition called inside class 

uscita dopo aver rimosso l'implementazione del protocollo da ViewController

protocol definition called inside extension 

Un modo per risolvere il problema è applicare l'estensione alla classe, quindi l'implementazione del protocollo sarà all'interno della classe. Ma questa non è un'estensione del protocollo.

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

} 
+0

Grazie per il tentativo, il motivo per cui voglio attenermi alle estensioni del protocollo è perché mi piacerebbe implementarlo su iOS e watchOS che don Non condividere alcuna classe di antenati. – TruMan1

0

Trovo questo comportamento un po 'strano. Comunque per il tuo problema attuale puoi usare questa soluzione.

Fondamentalmente l'idea è di introdurre un oggetto proxy in cui si implementa la logica per gestire la posizione in un protocollo e scrivere una classe conforme a tale protocollo. Quindi nel ViewController create un'istanza della classe (proxy) e implementate anche i metodi CLLocationManagerDelegate e passate semplicemente i metodi di callback all'oggetto proxy. Ciò ti aiuterà a separare la logica di gestione della posizione da ViewController. Puoi comunque utilizzare MyLocationManagerDelegateClass in modo indipendente nell'app Watch.

Spero che questo aiuti.

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

}