2015-02-19 15 views
7

Mam klasę, która musi wywołać delegata, gdy zmieni się jedna z jego właściwości. Oto uproszczona klasa i protokół dla delegata:Delegat Swift dla ogólnej klasy

protocol MyClassDelegate: class { 
    func valueChanged(myClass: MyClass) 
} 

class MyClass { 

    weak var delegate: MyClassDelegate? 

    var currentValue: Int { 
     didSet { 
      if let actualDelegate = delegate { 
       actualDelegate.valueChanged(self) 
      } 
     } 
    } 

    init(initialValue: Int) { 
     currentValue = initialValue 
    } 
} 

To wszystko działa dobrze. Ale chcę, aby ta klasa była ogólna. Tak, próbowałem to:

protocol MyClassDelegate: class { 
    func valueChanged(genericClass: MyClass) 
} 

class MyClass<T> { 

    weak var delegate: MyClassDelegate? 

    var currentValue: T { 
     didSet { 
      if let actualDelegate = delegate { 
       actualDelegate.valueChanged(self) 
      } 
     } 
    } 

    init(initialValue: T) { 
     currentValue = initialValue 
    } 
} 

To powoduje dwa błędy kompilatora. Po pierwsze, linia deklarująca valueChanged w protokole daje: Reference to generic type 'MyClass' requires arguments in <...>. Po drugie, wywołanie valueChanged w obserwatorze didSet powoduje: 'MyClassDelegate' does not have a member named 'valueChanged'.

myślałem użyciu typealias rozwiązałoby problem:

protocol MyClassDelegate: class { 
    typealias MyClassValueType 
    func valueChanged(genericClass: MyClass<MyClassValueType>) 
} 

class MyClass<T> { 

    weak var delegate: MyClassDelegate? 

    var currentValue: T { 
     didSet { 
      if let actualDelegate = delegate { 
       actualDelegate.valueChanged(self) 
      } 
     } 
    } 

    init(initialValue: T) { 
     currentValue = initialValue 
    } 
} 

I wydaje się być na dobrej drodze, ale nadal mam dwa błędy kompilatora. Drugi błąd z powyższego pozostaje, jak również nowy w linii deklarującej właściwość delegate z MyClass: Protocol 'MyClassDelegate' can only be used as a generic constraint because it has Self or associated type requirements.

Czy jest jakiś sposób, aby to osiągnąć?

Odpowiedz

23

Trudno wiedzieć co najlepszym rozwiązaniem jest twój problem bez konieczności więcej informacji, ale jednym z możliwych rozwiązań, aby zmienić swoje oświadczenie protokołu do tego:

protocol MyClassDelegate: class { 
    func valueChanged<T>(genericClass: MyClass<T>) 
} 

który usuwa potrzebę typealias w protokołu i powinien rozwiązać komunikaty o błędach, które otrzymujesz.

Jednym z powodów, dla których nie jestem pewien, czy jest to najlepsze rozwiązanie dla ciebie, jest to, że nie wiem, jak i gdzie jest wywoływana funkcja valueChanged, więc nie wiem, czy jest to praktyczne dodaj ogólny parametr do tej funkcji. Jeśli to rozwiązanie nie działa, opublikuj komentarz.

+0

To jest dokładnie to, co potrzebne ... dzięki. – GarlicFries

+0

Dla tego, co jest warte, 'valueChanged' jest wywoływane w powyższym kodzie w obserwatorze' didSet' na 'currentValue'. – GarlicFries

+0

Świetna odpowiedź. Udało mi się stworzyć delegata na moją generyczną klasę. Mam kolejne pytanie. Jak mogę rozróżnić, który elementowy delegat obiektu wywołuje funkcję delegowania ogólnego?thing1 or thing2? Proszę zobaczyć ten punkt. https://gist.github.com/zakkhoyt/0b2543bb5a5995b41e73bef83391c378 – VaporwareWolf

2

Protokoły mogą mieć wymagania dotyczące typów, ale nie mogą mieć charakteru ogólnego; i protokoły z wymaganiami typu mogą być używane jako ogólne ograniczenia, ale nie mogą być używane do wpisywania wartości. Z tego powodu nie będzie możliwe odwoływanie się do typu protokołu z klasy ogólnej, jeśli przejdziesz tę ścieżkę.

Jeśli protokół delegacja jest bardzo proste (jak jednego lub dwóch metod), można przyjąć zamknięć zamiast obiektu Protokół:

class MyClass<T> { 
    var valueChanged: (MyClass<T>) -> Void 
} 

class Delegate { 
    func valueChanged(obj: MyClass<Int>) { 
     print("object changed") 
    } 
} 

let d = Delegate() 
let x = MyClass<Int>() 
x.valueChanged = d.valueChanged 

można rozszerzyć pojęcie do struktury gospodarstwa pęczek zamknięć:

class MyClass<T> { 
    var delegate: PseudoProtocol<T> 
} 

struct PseudoProtocol<T> { 
    var valueWillChange: (MyClass<T>) -> Bool 
    var valueDidChange: (MyClass<T>) -> Void 
} 

Należy jednak zachować szczególną ostrożność przy zarządzaniu pamięcią, ponieważ bloki mają silne odniesienie do obiektu, do którego się odnoszą. Natomiast delegaci są zazwyczaj słabymi odniesieniami do unikania cykli.

+0

Działa idealnie, a dzięki za pomysł przekazania zamknięć. Odpowiedź Romana była pierwsza i rozwiązuje problem, który mam prostszy. – GarlicFries

3

Można używać szablonów z metod typu skasowaniem ...

protocol HeavyDelegate : class { 
    func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R 
} 

class Heavy<P, R> { 
    typealias Param = P 
    typealias Return = R 
    weak var delegate : HeavyDelegate? 
    func inject(p : P) -> R? { 
     if delegate != nil { 
      return delegate?.heavy(self, shouldReturn: p) 
     } 
     return nil 
    } 
    func callMe(r : Return) { 
    } 
} 
class Delegate : HeavyDelegate { 
    typealias H = Heavy<(Int, String), String> 

    func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R { 
     let h = heavy as! H // Compile gives warning but still works! 
     h.callMe("Hello") 
     print("Invoked") 
     return "Hello" as! R 
    } 
} 

let heavy = Heavy<(Int, String), String>() 
let delegate = Delegate() 
heavy.delegate = delegate 
heavy.inject((5, "alive")) 
Powiązane problemy