2016-01-21 31 views
9

faccio un protocollo:stoccaggio estensione Swift per i protocolli

protocol SomeProtocol { 
    func getData() -> String 
} 

Faccio una struct che si conforma ad esso:

struct SomeStruct: SomeProtocol { 
    func getData() -> String { 
     return "Hello" 
    } 
} 

Ora voglio che ogni UIViewController avere una proprietà chiamata source, così ho può fare qualcosa come ...

class MyViewController : UIViewController { 
    override func viewDidLoad() { 
     self.title = source.getData() 
    } 
} 

Per realizzare questo, creo un protocollo per d efine la proprietà:

protocol SomeProtocolInjectable { 
    var source: SomeProtocol! { get set } 
} 

Ora ho solo bisogno di estendere il controller della vista con questa proprietà:

extension UIViewController: SomeProtocolInjectable { 
    // ??? 
} 

Come posso incidere insieme una proprietà memorizzato che funzionerà con un tipo di protocollo?

Che cosa non ha funzionato:

  • var source: SomeProtocol!, ovviamente, non funziona perché le estensioni non hanno proprietà memorizzate
  • non posso use Objective-C associated objects perché un protocollo non è un oggetto
  • I impossibile wrap it in a class (funziona per altri tipi di valore, ma non per i protocolli)

Altri suggerimenti?

+0

L'utilizzo di una proprietà statica può funzionare? –

+0

Questa è una buona soluzione, ma idealmente i diversi controller di visualizzazione avrebbero diversi 'source's. Se vuoi lasciarlo come risposta, lo accetterò in un giorno o due se non c'è niente di meglio. –

+0

Vedere la mia risposta qui sotto - è possibile ottenere diversi 'sorgenti' per istanza usando un tipo * proxy * ... –

risposta

3

Qualsiasi oggetto protocollo può essere convertito in una classe di tipo cancellati. Costruisci un AnySomeProtocol e memorizzalo.

private var sourceKey: UInt8 = 0 

final class AnySomeProtocol: SomeProtocol { 
    func getData() -> String { return _getData() } 
    init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData } 
    private let _getData:() -> String 
} 

extension UIViewController: SomeProtocolInjectable { 
    var source: SomeProtocol! { 
     get { 
      return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol 
     } 
     set(newValue) { 
      objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN) 
     } 
    } 
} 

class MyViewController : UIViewController { 
    override func viewDidLoad() { 
     self.title = source.getData() 
    } 
} 

Il chiamante può utilizzare questo solo per accedere ai metodi del protocollo. Non puoi forzare il suo tipo originale con as, ma dovresti comunque evitarlo.

Come nota a margine, consiglierei davvero di effettuare source restituire SomeProtocol? anziché SomeProtocol!. Non c'è niente qui che prometta di impostare source. Non è nemmeno possibile impostarlo fino a viewDidLoad.

+0

Se sto capendo correttamente, questo copia lo stato da qualunque cosa sia stata implementata 'SomeProtocol'. Ma a parte questo, non sostituirebbe in modo efficace un 'SomeStruct' con un' AnySomeProtocol'? 'SomeStruct' avrà un'implementazione diversa. Ad esempio, un tipo potrebbe ottenere dati da un servizio Web, ma un altro dai dati principali. –

+0

Questo non copia lo stato. Si inoltra all'attuazione della struttura. _getData non è una stringa. È una funzione che restituisce una stringa. Anche se la struttura è inaccessibile, la chiusura cattura la struct (o cattura qualunque altra cosa tu superi che implementa questo protocollo). Questo è molto simile a AnySequence o AnyGenerator nello stdlib. Vedi http://robnapier.net/erasure –

+0

Grazie! Meraviglioso post sul blog. –

3

si può incidere in giro con una statica ed i controller di vista hash:

+0

Si noti che questo si basa su un comportamento non documentato. Funziona perché UIViewController implementa l'hash restituendo il puntatore dell'indirizzo, ma ciò non è promesso. Due controller di vista possono avere lo stesso hash (probabilmente non lo faranno). Invece è possibile indicizzare il puntatore all'oggetto: 'Unmanaged.passUnretained (self) .toOpaque()' (che è una chiave di dizionario valida, ma il puntatore è completamente non sicuro da usare). Si noti che questo approccio non libera mai i dati memorizzati, anche dopo la distruzione del controller della vista. –

+0

Intelligente! Ma sono d'accordo con @RobNapier, vorrei evitare di contare sul comportamento di hashing non documentato. +1 per la creatività però. –

0

ne dite di aggiungere un'implementazione di default per getData(), avere un'implementazione fittizia struct per il protocollo, e utilizzarlo come un valore predefinito per la variabile source:

protocol SomeProtocol { 
    func getData() -> String 
} 

extension SomeProtocol { 
    func getData() -> String { 
     return "Hello" 
    } 
} 

protocol SomeProtocolInjectable { 
    var source: SomeProtocol { get set } 
} 

struct DummyProtocolImplementation: SomeProtocol { 

} 

class MyViewController : UIViewController { 
    var _source: SomeProtocol = DummyProtocolImplementation() 

    override func viewDidLoad() { 
     self.title = source.getData() 
    } 
} 

extension MyViewController: SomeProtocolInjectable { 
    var source: SomeProtocol { get { return _source } set { _source = newValue } } 
} 

mi sono permesso di estendere MyViewController invece di UIViewController, in quanto quest'ultimo non conosce comunque il protocollo.

+0

Ciò richiede la dichiarazione di una proprietà su ogni sottoclasse di controller di visualizzazione, che è esattamente quello che sto cercando di evitare. –

+0

Non se si ereditano tutti i controller da 'MyViewController' – Cristik

Problemi correlati