2016-01-21 15 views
9

zrobię Protokół:Swift przechowywania rozszerzenie dla protokołów

protocol SomeProtocol { 
    func getData() -> String 
} 

robię struct zgodnego z nim:

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

teraz chcę za każdym UIViewController mieć właściwość o nazwie source, więc może zrobić coś takiego ...

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

Aby to osiągnąć, tworzę protokół do d efine właściwość:

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

Teraz wystarczy przedłużyć kontroler widok z tej właściwości:

extension UIViewController: SomeProtocolInjectable { 
    // ??? 
} 

Jak mogę włamać razem zapisaną właściwość, że będzie pracować z typem protokołu?

Co nie działa:

  • var source: SomeProtocol! oczywiście nie działa, ponieważ nie mają rozszerzenia zapisane właściwości
  • nie mogę use Objective-C associated objects ponieważ protokół nie jest obiektem
  • I nie może wrap it in a class (działa to dla innych typów wartości, ale nie dla protokołów)

Jakieś inne sugestie?

+0

Czy będziesz używać statycznej nieruchomości do pracy? –

+0

To dobre rozwiązanie, ale idealnie różne kontrolery widoku miałyby różne "źródła". Jeśli chcesz zostawić to jako odpowiedź, przyjmuję to za dzień lub dwa, jeśli nie przyjdzie nic lepszego. –

+0

Zobacz moją odpowiedź poniżej - możesz osiągnąć różne "źródło" na wystąpienie używając jakiegoś * proxy * typu ... –

Odpowiedz

3

Każdy obiekt protokół może być przekształcony w klasie typu-skasowane. Zbuduj AnySomeProtocol i zapisz to.

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

Dzwoniący może użyć tego tylko do uzyskania dostępu do metod protokołu. Nie można go przywrócić do pierwotnego typu za pomocą as, ale należy tego unikać.

Na marginesie, naprawdę polecam wykonanie source powrotu SomeProtocol? zamiast SomeProtocol!. Nie ma tu niczego, co przyniesie zapowiedź, że zostanie ustalona source. Nawet nie ustawisz go do viewDidLoad.

+0

Jeśli rozumiem to poprawnie, to kopiuje stan ze wszystkich zaimplementowanych 'SomeProtocol'. Ale poza tym, czy to nie zastąpiłoby 'SomeStruct'' AnySomeProtocol'? 'SomeStruct' będzie miało inną implementację. Na przykład jeden typ może pobierać dane z usługi WWW, a inny z danych podstawowych. –

+0

To nie kopiuje stanu. Przekazuje do implementacji struct. _getData nie jest łańcuchem. Jest to funkcja zwracająca ciąg. Mimo że struktura jest niedostępna, zamknięcie przechwytuje strukturę (lub przechwytuje to, co jeszcze przeszedłeś, implementując ten protokół). Jest to bardzo podobne do AnySequence lub AnyGenerator w stdlib. Zobacz http://robnapier.net/erasure –

+0

Dzięki! Wspaniały post na blogu też. –

3

można włamać się dookoła ze statyczną i kontrolerów widoku hash:

+0

Należy zauważyć, że opiera się to na nieudokumentowanym zachowaniu. Zdarza się, że działa, ponieważ UIViewController implementuje hash, zwracając jego wskaźnik adresu, ale to nie jest obiecane. Dwa kontrolery widoku mogą mieć ten sam skrót (prawdopodobnie po prostu nie będą). Zamiast tego możesz indeksować wskaźnik do obiektu: 'Unmanaged.passUnretained (self) .toOpaque()' (który jest poprawnym kluczem słownika, ale wskaźnik jest całkowicie niebezpieczny w użyciu). Należy zauważyć, że to podejście nigdy nie zwalnia przechowywanych danych, nawet po zniszczeniu kontrolera widoku. –

+0

Sprytny! Ale zgadzam się z @RobNapier, chciałbym uniknąć liczenia na nieudokumentowane zachowanie hashujące. +1 za kreatywność. –

0

Jak o dodanie domyślną implementację dla getData(), o realizację manekin struct dla protokołu, i użyć jej jako domyślnej wartości dla zmiennej 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 } } 
} 

Pozwoliłem sobie przedłużyć MyViewController zamiast UIViewController, ponieważ ten drugi nie wie o protokole.

+0

Wymaga to deklarowania właściwości na każdej podklasie kontrolera widoku, co jest dokładnie tym, czego próbuję uniknąć. –

+0

Nie, jeśli odziedziczysz wszystkie kontrolery z 'MyViewController' – Cristik

Powiązane problemy