2016-03-13 1 views
0

J'ai vraiment aimé la réponse de Sulthan (Anonymous class in swift) qui décrit la construction d'un objet conforme à un protocole mais dont la classe est cachée dans une fermeture. Cependant, lorsque j'essaie de faire quelque chose d'utile avec ça, j'échoue parce que la fermeture ne se ferme pas sur l'extérieur. les membres de l'instance de la classe à l'intérieur de la classe interne.Pourquoi les fermetures ne peuvent-elles pas se refermer sur les membres de l'instance

protocol EventListener { 
    func handleEvent(event: Int) ->() 
} 

class Recorder { 
    static var events = [Int]() // static is forced 
    var listener: EventListener = { 
     class R : EventListener { 
      func handleEvent(event: Int) { 
       events.append(event) 
       print("Recorded: \(event)") 
      } 
     }// 
     return R() 
    }() 
}// Recorder 

class Distributor { 
    var listeners = [EventListener]() 
    func register(listener: EventListener){ 
     listeners.append(listener) 
    } 
    func distribute(event: Int){ 
     for listener in listeners { 
      listener.handleEvent(event) 
     } 
    } 
} 

var d = Distributor() 
var r1 = Recorder() 
var r2 = Recorder() 

d.register(r1.listener) 
d.register(r2.listener) 
d.distribute(10) 

print(Recorder.events) // [10, 10] Same event recorded twice. 

Ce qui précède compile et s'exécute. Mais je veux events dans Recorder pour être un membre d'instance afin que chaque enregistreur ait son propre enregistrement. La suppression de static renvoie l'erreur du compilateur: le membre d'instance 'events' ne peut pas être utilisé.

J'ai essayé de définir une instance func record(event) dans Recorder pour handleEvent(event) pour appeler, mais j'obtiens la même erreur.

La réponse de Marius (en Instance member cannot be used on type | Closures) suggère que vous ne pouvez pas accéder aux membres d'instance pendant que les propriétés sont en cours de définition, donc j'ai aussi essayé de calculer l'écouteur plus tard comme ça.

class Recorder { 
    var events = [Int]() 
    var listener: EventListener { 
     class R : EventListener { 
      func handleEvent(event: Int) { 
       events.append(event) // error: can't access events 
       print("Recorded: \(event)") 
      } 
     } 
     return R() 
    } 
}// Recorder 

Mais le compilateur dit qu'il ne peut pas accéder au l'auto externe.

Les fermetures semblent assez impuissantes si elles ne peuvent pas accéder aux selfs externes. En Java, vous pouvez obtenir des selfs externes avec quelque chose comme Recorder.self.events. Et Recorder.self. ne sera peut-être nécessaire que s'il y a des conflits de noms (?)

Est-ce que Swift est conçu de cette façon ou qu'est-ce qui me manque?

Comment l'écririez-vous donc Recorder donne Distributor un objet qui ne peut rien faire mais recevoir des messages handleEvent?

Merci beaucoup.

Répondre

1

Je ne crois pas qu'il existe un moyen de fermer des événements dans un enregistreur partiellement initié. Cependant, vous pouvez créer un processus d'initialisation en deux étapes pour Recorder. La première étape crée l'objet Recorder avec un écouteur vide par défaut. La deuxième étape crée une fermeture correcte des événements et les affecte à l'écouteur. Cette implémentation ne possède pas de protocole EventListener. Je suis sûr qu'il existe un moyen simple de le faire, mais pour les classes imbriquées, il semble que c'est trop. Donc, au lieu de Distributeur contenant un tableau d'objets, contenant des fermetures. Nous laissons Distributor contenir le tableau des fermetures directement. À l'exception de l'appel supplémentaire à initialize, votre API ne change pas.

typealias EventListener = (Int) ->() 

class Distributor { 
    var listeners = [EventListener]() 
    func register(listener: EventListener){ 
    listeners.append(listener) 
    } 
    func distribute(event: Int){ 
    for listener in listeners { 
     listener(event) 
    } 
    } 
} 

class Recorder { 
    var events = [Int]() 
    var listener: EventListener = { 
    (event: Int) in 
    fatalError("Listener not initialized") 
    } 
    func initialize() { 
    listener = { 
     (event: Int) in 
     self.events.append(event) 
     print("Recorded: \(event)") 
    } 
    } 
} 

var d = Distributor() 

// 1st stage of Recorder instantiation 
var r1 = Recorder() 
var r2 = Recorder() 

// 2nd stage 
// r1.listener can now have a closure that includes an init'ed events 
r1.initialize() 
r2.initialize() 

d.register(r1.listener) 
d.register(r2.listener) 
d.distribute(10) 

print(r1.events) 
print(r2.events) 
+0

Merci de votre visite! Laisse-moi jouer avec ça un moment. – adazacom

0

En utilisant 2 l'initialisation de la scène de prix Ringo, et ayant Recorder passe self dans la classe interne, j'ai obtenu la cachette de classe que je cherchais. C'est un peu plus verbeux que j'espérais, mais il a l'avantage de rendre clair le cycle de vie des membres de l'instance de Recorder. Par exemple, il évite la confusion copy-vs-reference entourant events. self facilite également le transfert de données dans l'autre sens: adjustment.

Prix La solution de Ringo est plus élégante et adaptée à ce problème spécifique. Cette solution est plus générale et fonctionnera avec n'importe quel protocole existant. En général, j'essaie d'incorporer des motifs orientés protocole dans mon code. Cette solution crée des objets qui n'ont aucun autre type que le protocole auquel ils sont conformes.

protocol EventListener { 
    func handleEvent(event: Int) ->() 
} 

class Recorder { 
    var events = [Int]() // example of data out 
    let adjustment: Int // example of data in 
    var listener: EventListener = { 
     class __: EventListener { 
      func handleEvent(event: Int) { } 
     } 
     return __() 
    }() 
    init(adjustment: Int){ 
     self.adjustment = adjustment 
     initializeListener() 
    } 
    private func initializeListener() { 
     listener = { 
      class __: EventListener { 
       unowned var recorder: Recorder 
       init(recorder: Recorder){ 
        self.recorder = recorder 
       } 
       func handleEvent(event: Int) { 
        let adjustedEvent = 
          recorder.adjustment * event 
        recorder.events.append(adjustedEvent) 
       // print("Recorded: \(adjustedEvent)") 
       } 
      } 
      return __(recorder: self) 
     }() 
    } 
}// Recorder 

class Distributor { 
    var listeners = [EventListener]() 
    func addListener(listener: EventListener){ 
     listeners.append(listener) 
    } 
    func distributeEvent(event: Int){ 
     for listener in listeners { 
      listener.handleEvent(event) 
     } 
    } 
} 

var d = Distributor() 
var r1 = Recorder(adjustment: 2) 
var r2 = Recorder(adjustment: 3) 

d.addListener(r1.listener) 
d.addListener(r2.listener) 
d.distributeEvent(10) 

print(r1.events) // [20] 
print(r2.events) // [30] 

Si vous lisez encore, j'ai une question. J'ai fait membre de l'instance __>recorder sans propriétaire pour éviter la rétention. Dois-je aussi faire Recorder>listener sans propriétaire?