KVO using threads

  • Hi!

    Does KVO work when the changes are made in different thread?

    I'm changing a tooMany relationship using the add<Key>Object: method
    generated by the Xcode modeling tool.
    The changes however aren't reflected in my tableview. I've also tried
    using the mutableSetForKey:.

    I know my tableView is set up correctly because if I restart my
    application (causing all changes do flush to the SQLite store), every
    change I made shows up correctly!

    I've search the net for this and found that Bindings don't work with
    DO, does this means they don't work with threads?

    Paulo F. Andrade <52439...>
    mailto: <pfca...>
  • No. KVO messages are not threadsafe.

    On May 1, 2007, at 2:40 PM, Paulo F. Andrade wrote:

    > Hi!
    >
    > Does KVO work when the changes are made in different thread?
    >
    > I'm changing a tooMany relationship using the add<Key>Object: method
    > generated by the Xcode modeling tool.
    > The changes however aren't reflected in my tableview. I've also
    > tried using the mutableSetForKey:.
    >
    > I know my tableView is set up correctly because if I restart my
    > application (causing all changes do flush to the SQLite store),
    > every change I made shows up correctly!
    >
    > I've search the net for this and found that Bindings don't work with
    > DO, does this means they don't work with threads?
    >
    > Paulo F. Andrade <52439...>
    > mailto: <pfca...>
    >
  • Actually KVO and its messages are thread-safe.  But the objects that
    send (by having their properties changed, say) and receive (observers)
    the KVO notifications may not be thread-safe, or expect to receive the
    messages on different threads, which may require special additional
    actions to handle.  Such is the case with the Bindings-related objects
    (like the controllers) in AppKit.

    One general solution might be to interpose a proxy-like object between
    the model objects (presumably thread-safe) and the controllers.  I
    called this a receptionist pattern in my WWDC 2006 talk.  The
    receptionist object should implement observeValueForKeyPath:..., you
    should initialize the receptionist with the real object (say, a
    controller), and you should use the receptionist wherever you would
    otherwise refer to the controller.  (This last step is sometimes
    difficult to do in all cases.)  When the receptionist receives a KVO
    observeValue... call, it should save the parameters in a little
    private helper object, and send itself a private message to be
    performed on the main thread (performSelectorOnMainThread...) to
    deliver that message with its parameters to the real object (which
    will be on the main thread then).  No, I don't have an example which
    does this.

    That handles (if you can pull it off) the model -> controller -> view
    data flow direction.  Presumably if your model objects are thread-
    safe, changes on the main thread coming down from the view to the
    model objects will be fine.

    On the sending (KVO-notification-causing) side of things, using
    automatic KVO is not entirely thread-safe because the willChange/
    change/didChange combination is not atomic.  Using manual KVO (sending
    the willChange... and didChange... methods yourself in all the right
    places) you can make these atomic with your own lock (I suggest using
    the object itself as the lock with @synchronized() is a reasonable
    default choice of locking technique).  Using automatic KVO can, for
    example, make the change dictionary that observers get completely
    wrong with respect to the change that occurred due to execution
    ordering issues.

    Chris Kane
    Cocoa Frameworks, Apple

    On May 1, 2007, at 12:24 PM, Scott Anguish wrote:

    > No. KVO messages are not threadsafe.
    >
    > On May 1, 2007, at 2:40 PM, Paulo F. Andrade wrote:
    >
    >> Hi!
    >>
    >> Does KVO work when the changes are made in different thread?
    >>
    >> I'm changing a tooMany relationship using the add<Key>Object:
    >> method generated by the Xcode modeling tool.
    >> The changes however aren't reflected in my tableview. I've also
    >> tried using the mutableSetForKey:.
    >>
    >> I know my tableView is set up correctly because if I restart my
    >> application (causing all changes do flush to the SQLite store),
    >> every change I made shows up correctly!
    >>
    >> I've search the net for this and found that Bindings don't work
    >> with DO, does this means they don't work with threads?
    >>
    >> Paulo F. Andrade <52439...>
    >> mailto: <pfca...>
    >>

  • Hi,
    First of all, thank you very much for your help.

    I think I have understood your Receptionist pattern. However because,
    as you said, it is difficult to interpose the proxy-like object in
    all situations, I was thinking of doing it slightly different.
    I would subclass my controller (in my case an NSArrayController) and
    override the observeValueForKeyPath:... to call the super version of
    the method, using the perfomSelectorOnMainThread: .

    Because I can only send one argument using
    performSelectorOnMainThread: I would need a helper method to do this.
    Maybe it's better to write some code, here goes:

    @implementatiton ArrayControllerSubclass

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    object change:(NSDictionary *)change context:(void *)context
    {
    NSArray *args = [NSArray arrayWithObjects: keyPath, object, change,
    context, nil];
    //call the helper method
    [self performSelectorOnMainThread:@selector(auxiliaryMethod:)
    withObject:args waitUntilDone:NO modes:[NSArray
    arrayWithObject:NSDefaultRunLoopMode]];

    }

    - (void)auxiliaryMethod:(NSArray *)args
    {
    [super observeValueForKeyPath:[args objectAtIndex:0] ofObject:[args
    objectAtIndex:1] change:[args objectAtIndex:2] context:[args
    objectAtIndex:3]];
    }

    @end

    Before going about testing this, does anybody see something terribly
    wrong with this approach that I'm overlooking?

    As for the sending KVO side, I'm already using Core Data with my
    domain objects. So putting a synchronized(self){...} on the mutator
    methods would suffice to keep willChange and didChange ordered.

    Thank you for your time!

    Paulo F. Andrade <52439...>
    mailto: <pfca...>

    On 2007/05/01, at 22:11, Chris Kane wrote:

    > Actually KVO and its messages are thread-safe.  But the objects
    > that send (by having their properties changed, say) and receive
    > (observers) the KVO notifications may not be thread-safe, or expect
    > to receive the messages on different threads, which may require
    > special additional actions to handle.  Such is the case with the
    > Bindings-related objects (like the controllers) in AppKit.
    >
    > One general solution might be to interpose a proxy-like object
    > between the model objects (presumably thread-safe) and the
    > controllers.  I called this a receptionist pattern in my WWDC 2006
    > talk.  The receptionist object should implement
    > observeValueForKeyPath:..., you should initialize the receptionist
    > with the real object (say, a controller), and you should use the
    > receptionist wherever you would otherwise refer to the controller.
    > (This last step is sometimes difficult to do in all cases.)  When
    > the receptionist receives a KVO observeValue... call, it should
    > save the parameters in a little private helper object, and send
    > itself a private message to be performed on the main thread
    > (performSelectorOnMainThread...) to deliver that message with its
    > parameters to the real object (which will be on the main thread
    > then).  No, I don't have an example which does this.
    >
    > That handles (if you can pull it off) the model -> controller ->
    > view data flow direction.  Presumably if your model objects are
    > thread-safe, changes on the main thread coming down from the view
    > to the model objects will be fine.
    >
    > On the sending (KVO-notification-causing) side of things, using
    > automatic KVO is not entirely thread-safe because the willChange/
    > change/didChange combination is not atomic.  Using manual KVO
    > (sending the willChange... and didChange... methods yourself in all
    > the right places) you can make these atomic with your own lock (I
    > suggest using the object itself as the lock with @synchronized() is
    > a reasonable default choice of locking technique).  Using automatic
    > KVO can, for example, make the change dictionary that observers get
    > completely wrong with respect to the change that occurred due to
    > execution ordering issues.
    >
    >
    > Chris Kane
    > Cocoa Frameworks, Apple
    >
    >
    > On May 1, 2007, at 12:24 PM, Scott Anguish wrote:
    >
    >> No. KVO messages are not threadsafe.
    >>
    >> On May 1, 2007, at 2:40 PM, Paulo F. Andrade wrote:
    >>
    >>> Hi!
    >>>
    >>> Does KVO work when the changes are made in different thread?
    >>>
    >>> I'm changing a tooMany relationship using the add<Key>Object:
    >>> method generated by the Xcode modeling tool.
    >>> The changes however aren't reflected in my tableview. I've also
    >>> tried using the mutableSetForKey:.
    >>>
    >>> I know my tableView is set up correctly because if I restart my
    >>> application (causing all changes do flush to the SQLite store),
    >>> every change I made shows up correctly!
    >>>
    >>> I've search the net for this and found that Bindings don't work
    >>> with DO, does this means they don't work with threads?
    >>>
    >>> Paulo F. Andrade <52439...>
    >>> mailto: <pfca...>
    >>>

    >
  • Ok I have tested this now and can confirm that it works as expected.
    I had to make a slight change to the code earlier, here is the
    working version for future reference :)

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    object change:(NSDictionary *)change context:(void *)context
    {
    NSArray *args = [NSArray arrayWithObjects: keyPath, object, change,
    context, nil];
    //call the helper method
    [self performSelectorOnMainThread:@selector(auxiliaryMethod:)
    withObject:args waitUntilDone:NO modes:[NSArray
    arrayWithObject:NSDefaultRunLoopMode]];

    }

    - (void)auxiliaryMethod:(NSArray *)args
    {
    NSString *keyPath;
    id object;
    NSDictionary *change;
    void *context;

    @try {
      keyPath = [args objectAtIndex:0];
      object = [args objectAtIndex:1];
      change = [args objectAtIndex:2];
      context = [args objectAtIndex:3];
    }
    @catch (NSException * e) {
      // usually occurs because when constructing the NSArray args,
    context is nil
      // meaning that the array only has 3 positions instead of 4
    }
    @finally {
      [super observeValueForKeyPath:keyPath
                                ofObject:object
                                 change:change
                                 context:context];
    }
    }

    Paulo F. Andrade <52439...>
    mailto: <pfca...>

    On 2007/05/01, at 22:11, Chris Kane wrote:

    > Actually KVO and its messages are thread-safe.  But the objects
    > that send (by having their properties changed, say) and receive
    > (observers) the KVO notifications may not be thread-safe, or expect
    > to receive the messages on different threads, which may require
    > special additional actions to handle.  Such is the case with the
    > Bindings-related objects (like the controllers) in AppKit.
    >
    > One general solution might be to interpose a proxy-like object
    > between the model objects (presumably thread-safe) and the
    > controllers.  I called this a receptionist pattern in my WWDC 2006
    > talk.  The receptionist object should implement
    > observeValueForKeyPath:..., you should initialize the receptionist
    > with the real object (say, a controller), and you should use the
    > receptionist wherever you would otherwise refer to the controller.
    > (This last step is sometimes difficult to do in all cases.)  When
    > the receptionist receives a KVO observeValue... call, it should
    > save the parameters in a little private helper object, and send
    > itself a private message to be performed on the main thread
    > (performSelectorOnMainThread...) to deliver that message with its
    > parameters to the real object (which will be on the main thread
    > then).  No, I don't have an example which does this.
    >
    > That handles (if you can pull it off) the model -> controller ->
    > view data flow direction.  Presumably if your model objects are
    > thread-safe, changes on the main thread coming down from the view
    > to the model objects will be fine.
    >
    > On the sending (KVO-notification-causing) side of things, using
    > automatic KVO is not entirely thread-safe because the willChange/
    > change/didChange combination is not atomic.  Using manual KVO
    > (sending the willChange... and didChange... methods yourself in all
    > the right places) you can make these atomic with your own lock (I
    > suggest using the object itself as the lock with @synchronized() is
    > a reasonable default choice of locking technique).  Using automatic
    > KVO can, for example, make the change dictionary that observers get
    > completely wrong with respect to the change that occurred due to
    > execution ordering issues.
    >
    >
    > Chris Kane
    > Cocoa Frameworks, Apple
    >
    >
    > On May 1, 2007, at 12:24 PM, Scott Anguish wrote:
    >
    >> No. KVO messages are not threadsafe.
    >>
    >> On May 1, 2007, at 2:40 PM, Paulo F. Andrade wrote:
    >>
    >>> Hi!
    >>>
    >>> Does KVO work when the changes are made in different thread?
    >>>
    >>> I'm changing a tooMany relationship using the add<Key>Object:
    >>> method generated by the Xcode modeling tool.
    >>> The changes however aren't reflected in my tableview. I've also
    >>> tried using the mutableSetForKey:.
    >>>
    >>> I know my tableView is set up correctly because if I restart my
    >>> application (causing all changes do flush to the SQLite store),
    >>> every change I made shows up correctly!
    >>>
    >>> I've search the net for this and found that Bindings don't work
    >>> with DO, does this means they don't work with threads?
    >>>
    >>> Paulo F. Andrade <52439...>
    >>> mailto: <pfca...>
    >>>

    >