2015-05-28 7 views
18
@property (strong, nonatomic) UIViewController<UITableViewDelegate> *thing; 

Voglio realizzare una proprietà come in questo codice Objective-C a Swift. Così qui è che cosa ho provato:Swift Proprietà che è conforme a un protocollo e Classe

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController { 
    var thing: T! 
} 

Questo compila. Il mio problema arriva quando aggiungo proprietà dallo storyboard. Il tag @IBOutlet genera un errore del compilatore.

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController { 
    @IBOutlet weak var anotherThing: UILabel! // error 
    var thing: T! 
} 

L'errore:

Variable in a generic class cannot be represented in Objective-C 

Perchè sono attuando questo diritto? Cosa posso fare per risolvere o aggirare questo errore?

EDIT:

Swift 4, infine, ha una soluzione per questo problema. Vedi la mia risposta aggiornata.

+0

stavo cercando di farlo un paio di mesi fa e il consenso al momento era che non era possibile. Mi piacerebbe se non fosse corretto. – AdamPro13

risposta

11

Aggiornamento per Swift 4

Swift 4 ha aggiunto il supporto per la rappresentazione di un tipo come una classe che è conforme a un protocollo. La sintassi è Class & Protocol. Ecco qualche esempio di codice utilizzando questo concetto da "Novità di Swift" (sessione 402 dal WWDC 2017):

protocol Shakeable { 
    func shake() 
} 
extension UIButton: Shakeable { /* ... */ } 
extension UISlider: Shakeable { /* ... */ } 

// Example function to generically shake some control elements 
func shakeEm(controls: [UIControl & Shakeable]) { 
    for control in controls where control.isEnabled { 
     control.shake() 
    } 
} 

Come di Swift 3, questo metodo provoca problemi perché non è possibile passare nei tipi corretti. Se si tenta di passare in [UIControl], non ha il metodo shake. Se si tenta di passare in [UIButton], il codice viene compilato, ma non è possibile passare in alcun UISlider s. Se si passa in [Shakeable], quindi non è possibile controllare control.state, perché Shakeable non dispone di quello. Swift 4 ha finalmente affrontato l'argomento.

Vecchio risposta

io sono sempre intorno a questo problema per il momento con il seguente codice:

// This class is used to replace the UIViewController<UITableViewDelegate> 
// declaration in Objective-C 
class ConformingClass: UIViewController, UITableViewDelegate {} 

class AClass: UIViewController { 
    @IBOutlet weak var anotherThing: UILabel! 
    var thing: ConformingClass! 
} 

Questo sembra hacker a me.Se fosse necessario uno dei metodi delegati, avrei dovuto implementare tali metodi in ConformingClass (che NON voglio fare) e sovrascriverli in una sottoclasse.

Ho inviato questa risposta nel caso in cui qualcun altro si imbattesse in questo problema e la mia soluzione li aiuti, ma non sono soddisfatto della soluzione. Se qualcuno pubblicherà una soluzione migliore, accetterò la loro risposta.

+0

Devo fare qualcosa di sbagliato; sottoclassi della classe in '[Myclass & MyProtocol]' vengono brutalmente negate. – Jonny

+0

Qual è l'errore? – keithbhunter

+1

L'ho usato come tipo di una proprietà. Non sono sicuro, forse la sintassi non era corretta. Guardando il video di WWDC ora intorno alle 6:44 la sintassi per una proprietà è 'var client: (Someclass & Protocol)' – Jonny

0

è possibile dichiarare un delegato a Swift in questo modo:

weak var delegate : UITableViewDelegate? 

che possa funzionare con anche ibrida (Objective-C e veloce) del progetto. Il delegato dovrebbe essere opzionale & debole perché la sua disponibilità non è garantita e debole non crea il ciclo di conservazione.

Si riceve questo errore perché non ci sono generici in Objective-C e non consente di aggiungere la proprietà @IBOutlet.

Edit: 1. Forzare un tipo su delegato

Per forza che delegato è sempre un UIViewController è possibile implementare il setter personalizzato ed eccezione tiro quando il suo non è un UIViewController.

weak var _delegate : UITableViewDelegate? //stored property 
var delegate : UITableViewDelegate? { 
    set { 
     if newValue! is UIViewController { 
      _delegate = newValue 
     } else { 
      NSException(name: "Inavlid delegate type", reason: "Delegate must be a UIViewController", userInfo: nil).raise() 
     } 
    } 
    get { 
     return _delegate 
    } 
} 
+0

Ma se lo dichiaro solo come 'UITableViewDelegate', allora perdo l'altro requisito di essere un' UIViewController'.Voglio dichiarare che la proprietà è un 'UIViewController' conforme a' UITableViewDelegate'. – keithbhunter

+0

è possibile farlo definendo il metodo di setter personalizzato per delegato. Quando delegate non è un controller di visualizzazione, è possibile generare un'eccezione '[NSException raise: @" Formato non valido foo value ": @" foo di% d non è valido ", foo];' – kmithi

+0

Mi mancheranno le chiamate di metodo che potrei fare con un 'UIViewController'. Se è solo di tipo 'UITableViewDelegate', quindi non posso chiamare' self.delegate.dismissViewControllerAnimated() ', anche se sarà un' UIViewController'. – keithbhunter

5

Non è la soluzione ideale, ma è possibile utilizzare una funzione generica invece di una classe generica, come questo:

class AClass: UIViewController { 
     @IBOutlet weak var anotherThing: UILabel! 
     private var thing: UIViewController? 

     func setThing<T: UIViewController where T: UITableViewDelegate>(delegate: T) { 
     thing = delegate 
     } 
    } 
+0

La cosa più importante che voglio è poter chiamare uno dei metodi di classe o delegato dalla proprietà. Ad esempio, se 'thing' è un' UIViewController' che implementa 'UITableViewDelegate', voglio poter accedere a' thing.navigationController' AND 'thing.didSelectRowAtIndexPath()' senza dover cast 'thing' quando faccio la chiamata . Aveva senso? – keithbhunter

5

mi sono imbattuto lo stesso problema, e anche provato l'approccio generico. Alla fine l'approccio generico ha rotto l'intero progetto.

Dopo aver ripensato a questo problema, ho scoperto che un protocollo che non può essere utilizzato per specificare completamente un tipo (in altre parole, deve venire con informazioni di tipo aggiuntive come un tipo di classe) è improbabile che sia completo . Inoltre, sebbene lo stile Objc di dichiarare ClassType<ProtocolType> sia a portata di mano, non tiene conto del vantaggio dell'astrazione fornita dal protocollo poiché tale protocollo non aumenta realmente il livello di astrazione. Inoltre, se tale dichiarazione appare in più punti, deve essere duplicata. Ancora peggio, se più dichiarazioni di questo tipo sono correlate (probabilmente un singolo oggetto sarà passato attorno a loro), il programma diventa fragile e difficile da mantenere perché in seguito se la dichiarazione in un luogo deve essere cambiata, tutte le dichiarazioni correlate devono essere cambiato pure.

Soluzione

Se il caso d'uso di un immobile comporta sia un protocollo (diciamo ProtocolX) e alcuni aspetti di una classe (diciamo ClassX), il seguente approccio potrebbero essere prese in considerazione:

  1. Dichiarare un protocollo aggiuntivo che eredita da ProtocolX con i requisiti metodo/proprietà aggiunti che soddisfano automaticamente ClassX. Come nell'esempio seguente, un metodo e una proprietà sono i requisiti aggiuntivi, entrambi i quali soddisfano automaticamente UIViewController.

    protocol CustomTableViewDelegate: UITableViewDelegate { 
        var navigationController: UINavigationController? { get } 
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?) 
    } 
    
  2. dichiarare un protocollo aggiuntivo che eredita da ProtocolX con un ulteriore proprietà di sola lettura di tipo ClassX. Questo approccio non solo consente l'uso di nella sua interezza, ma mostra anche la flessibilità di non richiedere un'implementazione nella sottoclasse ClassX. Ad esempio:

    protocol CustomTableViewDelegate: UITableViewDelegate { 
        var viewController: UIViewController { get } 
    } 
    
    // Implementation A 
    class CustomViewController: UIViewController, UITableViewDelegate { 
    
        var viewController: UIViewController { return self } 
    
        ... // Other important implementation 
    } 
    
    // Implementation B 
    class CustomClass: UITableViewDelegate { 
    
        private var _aViewControllerRef: UIViewController // Could come from anywhere e.g. initializer 
        var viewController: UIViewController { return _aViewControllerRef } 
    
        ... // UITableViewDelegate methods implementation 
    } 
    

PS. I frammenti di cui sopra sono solo a scopo dimostrativo, miscelando UIViewController e UITableViewDelegate insieme non è consigliato.

Modifica per Swift 2+: Grazie per il commento di @ Shaps, è possibile aggiungere quanto segue per evitare di dover implementare la proprietà desiderata ovunque.

extension CustomTableViewDelegate where Self: UIViewController { 
    var viewController: UIViewController { return self } 
} 
+0

Questa è la migliore risposta che ho visto finora sull'argomento. Penso che i tuoi pensieri su come perdere i benefici dell'astrazione siano ben messi. Più ci penso, stavo cercando di scrivere codice Objective-C in una sintassi Swift, che era la vera fonte del problema. Grazie per la risposta approfondita! – keithbhunter

+1

Anche se sono d'accordo con i vostri commenti al 100% in linea di principio, non aiuta quando lavoriamo con classi Cocoa di cui non abbiamo alcun controllo. Se un metodo UIKit richiede che un parametro sia di tipo UIViewController e io sia un buon programmatore POP e richieda un protocollo per le mie funzionalità aggiunte, non c'è modo di aggirarlo. Ho bisogno di un oggetto che soddisfi un protocollo e un requisito di super classe. –

+1

Grazie per aver postato questo. Funziona perfettamente per il mio problema. Un'aggiunta ho aggiunto era 'estensione CustomTableViewDelegate dove Auto: UIViewController {var viewController: UIViewController {self ritorno}} ' Risparmio di dover implementare la var ovunque :) – Shaps