1

I have four obscure questions about the usual process of boxing weak references.

To demonstrate the issues, here's a notification system, Notes.

You'd use it like this...

class SyncedCell: UITableViewCell, Notes {

    override func layoutSubviews() {
        ...
        listen(forNote: "soloCell")
    }
    func editing() {
        say(note: "soloCell")
        ...
        input.becomeFirstResponder()
    }
    func note() {
        print("Hooray, I got a note..")
        editingCancel()
    }

So, the code for Notes is below.

For a given key (say "soloCell") you simply keep an array of references to any object which wants to get a message when that key is called.

Naturally, these have to be weak references.

So, when a new object arrives that needs to be memorized in the list...

    var b:_Box = _Box()
    b.p = self
    _notes[k]?.append(b)

(The "listen" function just adds that object to the list of items for that key. The "say" function runs through the list of listening items for that key: for each item - if the item has not gone away in the meantime - it sends a message.)

So! As far as I know, you cannot keep an array of weak references.

You have to box them up, as you see in the kodes below.

  1. Really, is that correct? Using a box is ugly, is there a way to just plain keep a list of weak references? Without having to box?

  2. Relatedly: in the kodes AnyObject is the base. Is that best?

  3. Notice the protocol is not : class. This is disturbing and I'm not sure if it should be.

  4. Note that very unfortunately in Swift - as far as I know - you cannot observe a weak reference going to nil. Is this still the case as of 2017? Is there any way at all to achieve this?

Footnote - regarding point 4, "is there any way to achieve this?" The only possibility seems to be to add an associatedPbject to the watched items. (Example of that)[https://stackoverflow.com/a/32607010/294884]

code...

struct _Box {
    weak var p: AnyObject?
    // note: I prefer to spell out the assigment,
    // rather than have a convenience initializer here
}

var _notes:[String:[_Box]] = [:]

protocol Notes {
    func note()
}

extension Notes where Self:AnyObject {

func listen(forNote k: String) {
    if _notes.index(forKey: k) == nil {
        _notes[k] = []
    }
    var b:_Box = _Box()
    b.p = self
    _notes[k]?.append(b)
}

func say(note k:String) {
    if let _n = _notes[k] {
        var k:Int = 0
        print("notes.4            saying......")
        for b in _n {
            let p = b.p
            if (p == nil) {
                print("\(k) notes.4            there's one that's been removed")
            }
            else {
                print("\(k) notes.4            sending ok...")
                (p as! Notes).note()
            }
            k = k + 1
        }
    }
    __noteCleaner()
}

func __noteCleaner() {
    for var (k, _n) in _notes {
        let kn = _n.count
        for i in (0..<kn).reversed() {
            let p = _n[i].p
            if (p == nil) {
                _n.remove(at: i)
                let newk = _n.count
                print("notes.4, removed a dud listener for key \(k) new length is \(newk)")
            }
        }
        if (_n.count == 0) {
            print("notes.4, removed a seemingly unused key \(k)")
            _notes.removeValue(forKey: k)
        }
    }
}

}

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • Related: you could also use function currying to hold on to a weak reference. I wrote up an event notification system based on this. http://www.folded-paper.com/home/2015/9/4/event-class-for-swift – Benzi Jan 30 '17 at 19:19
  • @benzi, thanks, that would take some thought. Maybe one day you could put in a very short answer here with the general idea in a sentence. I will review your article, thanks again. – Fattie Jan 30 '17 at 23:35

1 Answers1

1

I will concentrate on the heart of the problem.

You are trying to implement observer (listener) pattern by registering the observers on the subject. I guess the subject will then manually call every observer.

There is a simpler way to implement this. You can use a singleton. The observers will register at the singleton and the subject will notify the singleton when something important is happening.

There is special class for this, NotificationCenter. And it will keep only unowned references.

Delegates are not made for one observer, not for multiple observers.

Of course, there is a simple way to implement an array of weak references by wrapping the reference into a struct/object, see How do I declare an array of weak references in Swift? . I guess that's what your Box is doing?

However, I somehow think that your problem is caused by your architecture. You are mixing your view classes with your model classes and that's not a good idea.

To answer specifically your questions:

  1. No. I believe the discussion about this in the Swift mailing list ended with "you can wrap it". You don't have to wrap it so simply. You can write your own version of array that will do the wrapping internally.

  2. Use a generic instead of AnyObject, see the linked question above.

  3. Yes this is disturbing. This is possible because you actually only use it on AnyObject (that is, classes) and you are removing the types in Box. With Box.P declared as Note this wouldn't work unless Note were a class protocol.

  4. This is correct. See Know when a weak var becomes nil in Swift?. A simple workaround is to remove the listener manually when the listener is being deallocated (in deinit).

Community
  • 1
  • 1
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • HI Sulthan ! As you say, (1) it is a singleton. (_notes is a global - essentially a singleton). As you say (2) the observors register at the singleton (notice `listen#` in the example code at the top) (3) something notifies the singleton when something happens (notice`say#` in the example at top). – Fattie Jan 30 '17 at 22:06
  • Continuing, you mention `NSNotificationCenter` - it's really crap; I've never been on a team that uses it. In the Swift era one could basically say "never use it". – Fattie Jan 30 '17 at 22:07
  • Next! :) You mention that a common approach to storing an array of weak references, is by boxing them. That's true; I give a superb, flawless, example of this here in the sample code given. Regarding said common technique, I have four specific, detailed questions about that - see 1 through 4 ! – Fattie Jan 30 '17 at 22:09
  • Finally Sulthan! :) You mention a "problem", there is no problem whatsoever. In the final para you mention MVC. I'm not sure what you mean: the example of notifcations usage I give is, when you are syncing cells. The one and only way to do that is using notifications. (You can either use NSNotification, which sucks, or very much more likely, particularly in the Swift era, write your own.) – Fattie Jan 30 '17 at 22:13
  • @JoeBlow Your question is very complicated. I added answers to those specific questions. I don't agree that notifications "suck" :) – Sulthan Jan 30 '17 at 22:31
  • Fantastic, I definitely appreciate both your insights and general comments. Just TBC on **point 3**. Are you saying it's distrubing that it does *not* use `:class`. Your thinking is it should likely use `:class`. Am I correct? – Fattie Jan 30 '17 at 23:20
  • dude, all of my questions are complicated :-) – Fattie Jan 31 '17 at 00:40
  • 1
    @JoeBlow You cannot have a protocol stored into a `weak` variable unless the protocol is a class protocol. If your protocol were adopted by a struct, you wouldn't be able to store it weakly since structs are copied when stored. Therefore, you need a class protocol for `weak`. – Sulthan Jan 31 '17 at 08:48
  • FANTASTIC POINT - you should surely put that in your answer my man – Fattie Jan 31 '17 at 11:28