0

I really liked Sulthan's answer (in Anonymous class in swift) which describes building an object which conforms to a protocol but whose class is hidden inside a closure. This would be nice for building singletons and not polluting the name-space with classes like Recorder1, Recorder2...

However, when I try to do anything useful with it, I fail because the closure will not close over the outer class' instance members inside the inner class.

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.

The above compiles and runs. But I want events in Recorder to be an instance member so that each Recorder has its own record. Removing static throws the compiler error: instance member 'events' cannot be used.

I have tried defining an instance func record(event) in Recorder for handleEvent(event) to call, but I get the same error.

Marius's answer (in Instance member cannot be used on type | Closures) suggests you can't access instance members while properties are being defined so I also tried to compute listener later like this.

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

But the compiler says it can't access the the outer self.

Closures seem pretty impotent if they can't access outer selfs. In Java, you can get to outer selfs with something like Recorder.self.events. And Recorder.self. might only be necessary if there are name clashes(?)

Is Swift designed to be this way or what am I missing?

How would you write it so Recorder gives Distributor an object that can do nothing but receive handleEvent messages?

Thanks so much.

Community
  • 1
  • 1
adazacom
  • 443
  • 3
  • 9

2 Answers2

1

I don't believe that there is a way to close over events in a partially init'ed Recorder. However you can create a two stage initialization process for Recorder. Stage one creates the Recorder object with a default, empty listener. Stage two creates a proper closure over events and assigns it to listener.

This implementation does not have an EventListener protocol. I am sure there is a simple way to do that but for nested classes it seems overkill. So instead of Distributor containing an array of objects, containing closures. We are letting Distributor contain the array of closures directly.

Except for the additional call to initialize, your API does not change.

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)
Price Ringo
  • 3,424
  • 1
  • 19
  • 33
0

Using Price Ringo's 2 stage initialisation, and having Recorder pass self into the inner class, I have gotten the class hiding I was looking for. It is a little more verbose than I was hoping but has the advantage of making the life-cycle of Recorder's instance members clear. For example it avoids copy-vs-reference confusion surrounding events. self also makes it easy to pass data the other way: adjustment.

Price Ringo's solution is more elegant and appropriate to this specific problem. This solution is more general and will work with any existing protocol. In general, I am trying to incorporate protocol-oriented patterns into my code. This solution creates objects which have no type other than the protocol they conform to.

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]

If you are still reading, I have a question. I have made instance member __>recorder unowned to avoid retention. Do I also need to make Recorder>listener unowned?

adazacom
  • 443
  • 3
  • 9