UITextView as its own delegate - infinite loop on keyboard select

  • I have a UITextView subclass which, when you touch it to start editing, hangs in an infinite loop before the keyboard comes up. The subclass sets itself as its own delegate in the constructor (because the subclass in this instance wants to capture editing ended events and deal with them internally).

    I've stripped the class down to literally a subclass of UITextView which does nothing but set itself as delegate in the initWithRect: method (calling the superclass of course) and implements none of the delegate methods.

    Hitting pause I found the trace often in respondsToSelector: so I overrode that to print out what selector was being requested and then just call the superclass implementation, below is 8 milliseconds worth of that trace, it's just asking about 2 selectors over and over again, looping.

    2010-02-27 21:21:05.943 WhiteBoard[57954:207] Asking about selector: textViewDidChangeSelection:
    2010-02-27 21:21:05.944 WhiteBoard[57954:207] Asking about selector: keyboardInputChangedSelection:
    2010-02-27 21:21:05.947 WhiteBoard[57954:207] Asking about selector: textViewDidChangeSelection:
    2010-02-27 21:21:05.947 WhiteBoard[57954:207] Asking about selector: keyboardInputChangedSelection:
    2010-02-27 21:21:05.948 WhiteBoard[57954:207] Asking about selector: textViewDidChangeSelection:
    2010-02-27 21:21:05.948 WhiteBoard[57954:207] Asking about selector: keyboardInputChangedSelection:
    2010-02-27 21:21:05.949 WhiteBoard[57954:207] Asking about selector: textViewDidChangeSelection:
    2010-02-27 21:21:05.949 WhiteBoard[57954:207] Asking about selector: keyboardInputChangedSelection:
    2010-02-27 21:21:05.950 WhiteBoard[57954:207] Asking about selector: textViewDidChangeSelection:
    2010-02-27 21:21:05.951 WhiteBoard[57954:207] Asking about selector: keyboardInputChangedSelection:

    The stack trace at the point it does this is below - it starts at stackframe 10 only because I've trimmed off everything after the dummy respondsToSelector: call I put in which just NSLog()s, that takes 10 stack frames.

    #10 0x003ad281 in -[UITextView keyboardInputChangedSelection:] ()
    #11 0x0042f8dc in -[UIWebDocumentView keyboardInputChangedSelection:] ()
    #12 0x003ef907 in -[UIKeyboardImpl callChangedSelection] ()
    #13 0x003f5cdc in -[UIKeyboardImpl updateForChangedSelection] ()
    #14 0x003f3857 in -[UIKeyboardImpl setDelegate:force:] ()
    #15 0x003efeb5 in -[UIKeyboardImpl setDelegate:] ()

    There's obviously workarounds. I could start adding do-nothing delegate methods to try and break this up. I could have another object be the delegate and just call back into the delegate method handling code in my UITextView subclass, but that's a crappy contract to force any user of this object to conform to.

    I'm thinking to burn a support incident on this because it seems like wrong behaviour and I've found apple's support incident responses to be absolutely superb, before I do, is there something I have missed in the docs or elsewhere anyone know of which tells me what I'm doing is wrong or that if I want to be a self-delegate there are methods I must implement?

    Thanks

    Roland
  • On Sat, Feb 27, 2010 at 5:39 AM, Roland King <rols...> wrote:
    > I have a UITextView subclass which, when you touch it to start editing, hangs in an infinite loop before the keyboard comes up. The subclass sets itself as its own delegate in the constructor (because the subclass in this instance wants to capture editing ended events and deal with them internally).

    Do not make a UITextView its own delegate.

    More information here:
    http://lists.apple.com/archives/Cocoa-dev/2009/Jul/msg01406.html

    --Kyle Sluder
  • On 28-Feb-2010, at 9:21, Kyle Sluder <kyle.sluder...> wrote:

    >>
    >
    > Do not make a UITextView its own delegate.
    >
    > More information here:
    > http://lists.apple.com/archives/Cocoa-dev/2009/Jul/msg01406.html
    >
    > --Kyle Sluder

    Thanks that's what I see too. That thread doesn't explain why of
    course. I have a trivial test case so I'll file this as a bug and see
    what comes back because I think the behaviour is wrong. For now I'm
    using a different object as delegate, which isn't so neat but works
    fine.
  • Oh and one other point after reading that whole thread (on an iPhone
    that's surprisingly hard!), the application does infinite loop but it
    doesn't recurse, so I do not believe the suggestion in the middle of
    that thread that respondsToSelector: checks self and then the delegate
    (which is itself in this case) and thus recurses, is correct. The
    stack depth stays the same and the respondsToSelector calls alternate.

    Nor do I think that's the delegate pattern either, if it were I'd just
    have to implement the delegate method in my subclass but not make it
    it's own delegate and it would work, but it doesn't. Wish it did tho,
    that would be a useful pattern sometimes and I'd be able to still
    chain the real delegate later if I wanted.

    On 28-Feb-2010, at 10:37, Roland King <rols...> wrote:

    >
    >
    >
    >
    > On 28-Feb-2010, at 9:21, Kyle Sluder <kyle.sluder...> wrote:
    >
    >>>
    >>
    >> Do not make a UITextView its own delegate.
    >>
    >> More information here:
    >> http://lists.apple.com/archives/Cocoa-dev/2009/Jul/msg01406.html
    >>
    >> --Kyle Sluder
    >
    > Thanks that's what I see too. That thread doesn't explain why of
    > course. I have a trivial test case so I'll file this as a bug and
    > see what comes back because I think the behaviour is wrong. For now
    > I'm using a different object as delegate, which isn't so neat but
    > works fine.
  • On Sat, Feb 27, 2010 at 6:37 PM, Roland King <rols...> wrote:
    > Thanks that's what I see too. That thread doesn't explain why of course. I
    > have a trivial test case so I'll file this as a bug and see what comes back
    > because I think the behaviour is wrong. For now I'm using a different object
    > as delegate, which isn't so neat but works fine.

    The thread does explain exactly what's going on in the UITextField
    case: it sends itself messages which are also sent to the delegate.

    In your case, -[UITextView keyboardInputChangedSelection:] calls [self
    textViewDidChangeSelection:], which in turn asks if [self.delegate
    respondsToSelector:@selector(textViewDidChangeSelection:)], which
    obviously returns YES, so it calls [self.delegate
    textViewDidChangeSelection:]. Because self == delegate, you infinitely
    recurse.

    --Kyle Sluder
  • On 01-Mar-2010, at 9:29 AM, Kyle Sluder wrote:

    > On Sat, Feb 27, 2010 at 6:37 PM, Roland King <rols...> wrote:
    >> Thanks that's what I see too. That thread doesn't explain why of course. I
    >> have a trivial test case so I'll file this as a bug and see what comes back
    >> because I think the behaviour is wrong. For now I'm using a different object
    >> as delegate, which isn't so neat but works fine.
    >
    > The thread does explain exactly what's going on in the UITextField
    > case: it sends itself messages which are also sent to the delegate.
    >
    > In your case, -[UITextView keyboardInputChangedSelection:] calls [self
    > textViewDidChangeSelection:], which in turn asks if [self.delegate
    > respondsToSelector:@selector(textViewDidChangeSelection:)], which
    > obviously returns YES, so it calls [self.delegate
    > textViewDidChangeSelection:]. Because self == delegate, you infinitely
    > recurse.
    >

    There's two reasons that theory doesn't appear to be right in this case.

    Firstly if that's what happened, the program would recurse, the stack would be full of textViewDidChangeSelection: calls each calling the next, but that is not what happens, the program hangs, the code infinitely loops, but it does not recurse, the stack stays about 20 stack frames deep.

    Secondly, respondsToSelector:@selector(textViewDidChangeSelection:) doesn't return YES, it returns NO (which is what I'd expect because I haven't implemented it or anything else come to that matter). UITextView does not implement or respond to textViewDidChangeSelection:. (UITextView does implement the other method in that trace, keyboardInputChangedSelection: but that's not a delegate method, it's not even a documented method so it must be private.)

    I think that thread is also confused about the delegate pattern as used in cocoa and mixes it up with the more general decorator pattern. The notion stated early in that thread is that delegation means if you yourself do not respond to a given selector, you ask your delegate if it responds to it. That may be close to what decoration means, but it's not cocoa delegation where there are a set of selectors your delegate may/must respond to, but they are in general a different set of selectors from those your class implements and they are conditionally called on the delegate object only, not the class itself, during the processing of a usually entirely differently named method.

    I agree that if a class implemented one of its own delegate methods and if that implementation called the delegate's implementation of that method without checking for delgate==self and if you then went to set delegate=self you'd recurse (I think if a class allowed that to happen by not checking that it would be a bug by the way). That's not what's happening here however, UITextView doesn't implement the delegate method, it's not being called and the code isn't recursing, it's looping for a different reason.

    I filed this one as a bug, I'll see what comes back and if I get a 'works correctly' on it I'll go burn up a support incident, I have two and they're expiring all too soon anyway. If anything interesting comes out of it I'll follow up.
  • On 01.03.2010, at 12:05, Roland King wrote:
    > Firstly if that's what happened, the program would recurse, the stack would be full of textViewDidChangeSelection: calls each calling the next, but that is not what happens, the program hangs, the code infinitely loops, but it does not recurse, the stack stays about 20 stack frames deep.

    I haven't read that thread, so you may well be true, but just going by your description, tail recursion optimizations, as well as IMP cacheing, could cause the behaviour you're observing.

    Cheers,
    -- Uli Kusterer
    "The witnesses of TeachText are everywhere..."
  • On 01-Mar-2010, at 7:34 PM, Uli Kusterer wrote:

    > On 01.03.2010, at 12:05, Roland King wrote:
    >> Firstly if that's what happened, the program would recurse, the stack would be full of textViewDidChangeSelection: calls each calling the next, but that is not what happens, the program hangs, the code infinitely loops, but it does not recurse, the stack stays about 20 stack frames deep.
    >
    >
    > I haven't read that thread, so you may well be true, but just going by your description, tail recursion optimizations, as well as IMP cacheing, could cause the behaviour you're observing.
    >

    That is true, if the method actually existed at all and was called and recursed, those optimizations could look like that, thanks for pointing it out.

    In this case there's the extra point that the method doesn't actually exist and it's not being called at all (it's just being checked for in respondsToSelector:)
previous month february 2010 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Go to today