2016-01-21 12 views
9

Ich mache ein Protokoll:Swift Erweiterung Speicher für Protokolle

protocol SomeProtocol { 
    func getData() -> String 
} 

ich eine Struktur zu machen, die ihm entspricht:

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

Jetzt möchte ich jeden UIViewController eine Eigenschaft haben source genannt, so dass ich kann etwas tun wie ...

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

Um dies zu erreichen, erstelle ich ein Protokoll zu d efine die Eigenschaft:

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

Jetzt brauche ich nur die View-Controller mit dieser Eigenschaft erweitern:

extension UIViewController: SomeProtocolInjectable { 
    // ??? 
} 

Wie kann ich zusammen eine gespeicherte Eigenschaft hacken, die mit einem Protokolltyp funktionieren wird?

Was nicht funktioniert hat:

  • var source: SomeProtocol! offensichtlich nicht funktioniert, weil Erweiterungen haben keine gespeicherten Eigenschaften
  • Ich kann nicht use Objective-C associated objects weil ein Protokoll kein Objekt ist
  • I kann nicht wrap it in a class (dies für andere Werttypen funktioniert, aber nicht Protokolle)

Alle anderen Vorschläge?

+0

Wird die Verwendung einer statischen Eigenschaft für Sie funktionieren? –

+0

Das ist ein guter Workaround, aber im Idealfall würden verschiedene View-Controller unterschiedliche Quellen haben. Wenn Sie es als Antwort verlassen wollen, werde ich es in ein oder zwei Tagen akzeptieren, wenn nichts Besseres kommt. –

+0

Siehe meine Antwort unten - Sie können verschiedene "Quelle" pro Instanz mit einigen * Proxy * -Typ erreichen ... –

Antwort

3

Jedes Objekt-Protokoll kann in eine Typ-gelöschten Klasse umgewandelt werden. Erstellen Sie eine AnySomeProtocol und speichern Sie diese.

Der Aufrufer kann nur diese verwenden, um auf die Protokollmethoden zuzugreifen. Sie können es nicht mit as in seinen ursprünglichen Typ zurückdrängen, aber Sie sollten das sowieso vermeiden.

Als Randbemerkung, würde ich als SomeProtocol! empfehlen machen source Rückkehr SomeProtocol? eher wirklich. Es gibt hier nichts, was verspricht, dass source gesetzt wird. Sie setzen es nicht einmal bis viewDidLoad.

+0

Wenn ich das richtig verstehe, kopiert dies den Zustand von jedem implementierten "SomeProtocol". Aber würde das nicht effektiv eine "SomeStruct" mit einem "AnySomeProtocol" ersetzen? 'SomeStruct' wird eine andere Implementierung haben. Zum Beispiel könnte ein Typ Daten von einem Web-Service erhalten, aber ein anderer von Core Data. –

+0

Dies kopiert den Status nicht. Es wird an die Implementierung der Struktur weitergeleitet. _getData ist kein String. Es ist eine Funktion, die einen String zurückgibt. Obwohl auf die Struktur nicht zugegriffen werden kann, erfasst die Schließung die Struktur (oder erfasst, was auch immer Sie übergeben, das dieses Protokoll implementiert). Das ist sehr ähnlich wie AnySequence oder AnyGenerator in der stdlib. Siehe http://robnapier.net/erasure –

+0

Danke! Wunderbarer Blogbeitrag auch. –

3

Sie können hash mit einem statischen und dem View-Controller-Hack um:

+0

Beachten Sie, dass dies auf undokumentiertem Verhalten beruht. Es funktioniert, weil UIViewController Hash durch Rückgabe seines Adresszeigers implementiert, aber das ist nicht versprochen. Zwei View-Controller dürfen den gleichen Hash haben (sie werden es wahrscheinlich nicht). Stattdessen könnten Sie den Zeiger auf das Objekt indizieren: 'Unmanaged.passUnready (self) .toOpaque()' (was ein gültiger Wörterbuchschlüssel ist, aber der Zeiger ist völlig unsicher zu verwenden). Beachten Sie, dass dieser Ansatz niemals die gespeicherten Daten freigibt, selbst nachdem der View-Controller zerstört wurde. –

+0

Clever! Aber ich stimme @RobNapier zu, ich möchte vermeiden, auf das undokumentierte Hash-Verhalten zu zählen. +1 für Kreativität. –

0

Wie wäre es eine Standardimplementierung für getData() hinzufügen, eine Dummy-Struktur-Implementierung für das Protokoll aufweist und verwenden, die als Standardwert für die source Variable:

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

ich mir die Freiheit MyViewController anstatt zu verlängern nahm von UIViewController, da letzteres sowieso nichts über das Protokoll weiß.

+0

Dazu muss eine Eigenschaft für jede View-Controller-Unterklasse deklariert werden, was genau das ist, was ich vermeiden möchte. –

+0

Nicht, wenn Sie alle Ihre Controller von 'MyViewController' übernehmen – Cristik