NSInvocation question

  • Dear list,

    I have a document-based application, and my NSDocument subclass runs
    an experiment which amounts to invoking NSInvocations based on certain
    conditions. These NSInvocations retain their arguments, and the target
    of the NSInvocation is the NSDocument subclass. I need to record these
    invocations as well, so I made a class MVAction as follows:

    @interface MVAction : NSObject <NSCoding> {
    NSInvocation *action;
    double invocationTime;
    NSString *type;
    NSArray *arguments;
    }

    The type is a string representation of the selector used in the
    NSInvocation and the arguments are the arguments of the NSInvocation.
    I did it like this because you can't encode an NSInvocation, and I
    don't need the actual invocation anyway. My document has an instance
    variable NSMutableArray *actions in which these MVActions are stored.

    The problem I have is this: when I close my document, it isn't
    deallocated. If the invocations don't retain their arguments, that
    problem is gone, but I do need to retain them. What's a good way to
    solve this? I could release the NSInvocation *action when I put an
    MVAction in the array, but I wonder if there's a better method.

    Any help greatly appreciated!

    Thanks,
    Hank

    Hank Heijink
    <hank.list...>
  • On Feb 21, 2008, at 18:22, Hank Heijink wrote:

    > I have a document-based application, and my NSDocument subclass
    > runs an experiment which amounts to invoking NSInvocations based on
    > certain conditions. These NSInvocations retain their arguments, and
    > the target of the NSInvocation is the NSDocument subclass. I need
    > to record these invocations as well, so I made a class MVAction as
    > follows:
    >
    > @interface MVAction : NSObject <NSCoding> {
    > NSInvocation *action;
    > double invocationTime;
    > NSString *type;
    > NSArray *arguments;
    > }
    >
    > The type is a string representation of the selector used in the
    > NSInvocation and the arguments are the arguments of the
    > NSInvocation. I did it like this because you can't encode an
    > NSInvocation, and I don't need the actual invocation anyway. My
    > document has an instance variable NSMutableArray *actions in which
    > these MVActions are stored.
    >
    > The problem I have is this: when I close my document, it isn't
    > deallocated. If the invocations don't retain their arguments, that
    > problem is gone, but I do need to retain them. What's a good way to
    > solve this? I could release the NSInvocation *action when I put an
    > MVAction in the array, but I wonder if there's a better method.

    You have a retain cycle - the document retain the array which retain
    the invocation, which retain the document.

    If you save the target, selector and arguments, you can invoke the
    selector without NSInvocation. Don't retain the target.

    Best Regards,

    Nir Soffer
  • On Feb 21, 2008, at 7:42 PM, Nir Soffer wrote:
    >>
    >> The problem I have is this: when I close my document, it isn't
    >> deallocated. If the invocations don't retain their arguments, that
    >> problem is gone, but I do need to retain them. What's a good way to
    >> solve this? I could release the NSInvocation *action when I put an
    >> MVAction in the array, but I wonder if there's a better method.
    >
    >
    > You have a retain cycle - the document retain the array which retain
    > the invocation, which retain the document.

    Ah. I didn't realize that an NSInvocation retains not only its
    arguments, but also its target (the document only occurs as target,
    never as argument). The documentation doesn't mention this, as far as
    I can tell.

    > If you save the target, selector and arguments, you can invoke the
    > selector without NSInvocation. Don't retain the target.

    I made NSInvocations because timing is critical and I didn't want to
    spend the time constructing the call when it needs to be invoked. I
    haven't profiled the difference yet though, so maybe the tradeof isn't
    bad. I'll give it a whirl.

    Thanks!

    Hank Heijink
    <hank.list...>
  • On 22/02/2008, at 3:00 PM, Hank Heijink wrote:

    > I made NSInvocations because timing is critical and I didn't want to
    > spend the time constructing the call when it needs to be invoked. I
    > haven't profiled the difference yet though, so maybe the tradeof
    > isn't bad. I'll give it a whirl.

    I'm not sure what exactly you're trying to do, but NSInvocation isn't
    that fast a way of making a call. It's certainly going to somewhat
    slower than a compiler generated call and it's not NSInvocation's
    intended use.

    If you want fast calls, you should use -[NSObject methodSelector:],
    cache the result and then call it directly with whatever arguments you
    want. Make sure you read the documentation to see an example as it
    usually requires a cast.

    Having said that, let me say what has been said many times before,
    unless you know it's going to be a performance bottleneck, you should
    be writing your code so that it's as readable/maintainable as possible
    and then optimise for performance if necessary.

    - Chris
  • On Feb 21, 2008, at 11:18 PM, Chris Suter wrote:

    > I'm not sure what exactly you're trying to do, but NSInvocation
    > isn't that fast a way of making a call. It's certainly going to
    > somewhat slower than a compiler generated call and it's not
    > NSInvocation's intended use.

    I should have realized that when NSInvocation started giving me
    trouble...

    > If you want fast calls, you should use -[NSObject methodSelector:],
    > cache the result and then call it directly with whatever arguments
    > you want. Make sure you read the documentation to see an example as
    > it usually requires a cast.
    >
    > Having said that, let me say what has been said many times before,
    > unless you know it's going to be a performance bottleneck, you
    > should be writing your code so that it's as readable/maintainable as
    > possible and then optimise for performance if necessary.

    The nice thing about NSInvocation is not so much its efficiency (or
    lack thereof, as the case may be), but the fact that once you've got
    the thing constructed, you can just call invoke on it, without having
    to think about what's inside. I have functions of zero, one, or two
    arguments that I wrapped up in NSInvocations. With IMPs, I have to
    check the number of arguments both for the typing and for the call.

    However, even with the check for number of arguments, an IMP is very
    very fast.

    Thanks!

    Hank

    Hank Heijink
    <hank.list...>
  • On 23/02/2008, at 2:36 AM, Hank Heijink wrote:

    > The nice thing about NSInvocation is not so much its efficiency (or
    > lack thereof, as the case may be), but the fact that once you've got
    > the thing constructed, you can just call invoke on it, without
    > having to think about what's inside. I have functions of zero, one,
    > or two arguments that I wrapped up in NSInvocations. With IMPs, I
    > have to check the number of arguments both for the typing and for
    > the call.
    >
    > However, even with the check for number of arguments, an IMP is very
    > very fast.

    What you say here doesn't make sense to me. Without knowing exactly
    what you're trying to do, it's difficult for me to comment. You can
    arrange things so that it's simple without using NSInvocations. It
    sounds like you've got some kind of delegate pattern, so the usual
    Cocoa way of doing this is to pass a selector and a target around. For
    example:

    - (void)myCallBack1
    {
      /* You can call another method here with whatever arguments you
    want. The arguments
          would be stored as instance variables of the object. */
    }

    - (void)myCallBack2
    {
      // Likewise, you can call another method here with whatever
    arguments you want
    }

    - (void)myCallBack3
    {
      // Do something
    }

    Elsewhere you'd have something like:

    [myObject doSomethingWithDelegate:target selector:@selector
    (myCallBack1)];

    Then to make the callback you'd simply do:

    [target performSelector:selector];

    If you want to speed things up, you'd get and cache the IMP. That's
    about as simple as doing [NSInvocation invoke] and there's no need to
    be doing any checking of number of arguments.

    Kind regards,

    Chris
  • On Feb 22, 2008, at 8:47 PM, Chris Suter wrote:

    >
    > On 23/02/2008, at 2:36 AM, Hank Heijink wrote:
    >
    >> The nice thing about NSInvocation is not so much its efficiency (or
    >> lack thereof, as the case may be), but the fact that once you've
    >> got the thing constructed, you can just call invoke on it, without
    >> having to think about what's inside. I have functions of zero, one,
    >> or two arguments that I wrapped up in NSInvocations. With IMPs, I
    >> have to check the number of arguments both for the typing and for
    >> the call.
    >>
    >> However, even with the check for number of arguments, an IMP is
    >> very very fast.
    >
    > What you say here doesn't make sense to me. Without knowing exactly
    > what you're trying to do, it's difficult for me to comment. You can
    > arrange things so that it's simple without using NSInvocations. It
    > sounds like you've got some kind of delegate pattern, so the usual
    > Cocoa way of doing this is to pass a selector and a target around.
    > For example:

    [examples deleted]

    I haven't been very clear, my apologies. I may have completely
    overlooked the best strategy, so let me try to explain what I'm doing.
    I have to call methods depending on certain conditions. These include
    passing of time, movement of the cursor, speed of the cursor, etc.

    All those methods are on MyDocument. For example:

    - (void)makeGraphic:(MVGraphic *)aGraphic changeStatusTo:(NSNumber
    *)newStatus;
    - (void)startTrial:(MVTrial *)trial;

    The reason I liked NSInvocations is that just before running the
    experiment, I could wrap the selector and the arguments in an
    invocation and when running the experiment, I could call -[invoke] on
    both without knowing anything about which method is being called and
    how many arguments it has.

    Assume I have an object with the arguments as instance variables. When
    I'm using an IMP or -[performSelector] variants, I seem to need
    something like this:

    switch (numberOfArguments) {
    case 0:
      // callBackAsImp has type void (*)(id, SEL)
      callBackAsImp();
      break;
    case 1:
      // callBackAsImp has type void (*)(id, SEL, id)
      callBackAsImp(argument1);
      break;
    case 2:
      // callBackAsImp has type void (*)(id, SEL, id, id)
      callBackAsImp(argument1, argument2);
      break;
    }

    When I'm constructing the callBackAsImp, I'd need to have another case
    statement to type it correctly. Does this make sense? Maybe I'm
    missing the point here, but I haven't figured out how to get around
    this yet.

    Thanks again,
    Hank

    Hank Heijink
    <hank.list...>
  • On Feb 21, 2008, at 18:22, Hank Heijink wrote:

    > I have a document-based application, and my NSDocument subclass
    > runs an experiment which amounts to invoking NSInvocations based on
    > certain conditions. These NSInvocations retain their arguments, and
    > the target of the NSInvocation is the NSDocument subclass. I need
    > to record these invocations as well, so I made a class MVAction as
    > follows:
    >
    > @interface MVAction : NSObject <NSCoding> {
    > NSInvocation *action;
    > double invocationTime;
    > NSString *type;
    > NSArray *arguments;
    > }
    >
    > The type is a string representation of the selector used in the
    > NSInvocation and the arguments are the arguments of the
    > NSInvocation. I did it like this because you can't encode an
    > NSInvocation, and I don't need the actual invocation anyway. My
    > document has an instance variable NSMutableArray *actions in which
    > these MVActions are stored.
    >
    > The problem I have is this: when I close my document, it isn't
    > deallocated. If the invocations don't retain their arguments, that
    > problem is gone, but I do need to retain them. What's a good way to
    > solve this? I could release the NSInvocation *action when I put an
    > MVAction in the array, but I wonder if there's a better method.

    Another fix - when your document is closed, release the array holding
    the MVActions, which retain the invocation. This will break the
    retain cycle and allow your document to be released.

    Best Regards,

    Nir Soffer
  • That's essentially what I ended up doing: I need to save the other
    three instance variables of the MVActions, so for now I release the
    NSInvocation ivar before adding an MVAction to the array. Not very
    elegant maybe, but it does solve my problem.

    Thanks,
    Hank

    On Feb 25, 2008, at 3:30 PM, Nir Soffer wrote:

    > Another fix - when your document is closed, release the array
    > holding the MVActions, which retain the invocation. This will break
    > the retain cycle and allow your document to be released.
    >
    >
    > Best Regards,
    >
    > Nir Soffer
    >

    Hank Heijink
    <hank.list...>
  • On Feb 21, 2008, at 18:22, Hank Heijink wrote:
    > I have a document-based application, and my NSDocument subclass
    > runs an experiment which amounts to invoking NSInvocations based on
    > certain conditions. These NSInvocations retain their arguments, and
    > the target of the NSInvocation is the NSDocument subclass. I need
    > to record these invocations as well, so I made a class MVAction as
    > follows:
    >
    > @interface MVAction : NSObject <NSCoding> {
    > NSInvocation *action;
    > double invocationTime;
    > NSString *type;
    > NSArray *arguments;
    > }
    >
    > The type is a string representation of the selector used in the
    > NSInvocation and the arguments are the arguments of the
    > NSInvocation. I did it like this because you can't encode an
    > NSInvocation, and I don't need the actual invocation anyway. My
    > document has an instance variable NSMutableArray *actions in which
    > these MVActions are stored.
    >
    > The problem I have is this: when I close my document, it isn't
    > deallocated. If the invocations don't retain their arguments, that
    > problem is gone, but I do need to retain them. What's a good way to
    > solve this? I could release the NSInvocation *action when I put an
    > MVAction in the array, but I wonder if there's a better method.

    Another fix (re-reading the docs helps) - you can create an
    invocation without a target, since you don't want to retain the
    target in this case, and the target is known anyway. Then you can use
    -invokeWithTarget:myDocument instead of -invoke.

    The docs don't tell if -invokeWithTarget: will retain the target, so
    call -setTarget:nil after invoking to be sure you don't create a
    retain cycle.

    Best Regards,

    Nir Soffer
  • On Feb 25, 2008, at 3:49 PM, Nir Soffer wrote:

    > The docs don't tell if -invokeWithTarget: will retain the target, so
    > call -setTarget:nil after invoking to be sure you don't create a
    > retain cycle.

    As far as I understand the documentation, -[invokeWithTarget:] is
    equivalent to -[setTarget:] followed by -[invoke]. That would mean the
    target is retained, according to the doc for -[retainArguments].

    Hank Heijink
    <hank.list...>
  • On 26/02/2008, at 1:49 AM, Hank Heijink wrote:

    > I haven't been very clear, my apologies. I may have completely
    > overlooked the best strategy, so let me try to explain what I'm
    > doing. I have to call methods depending on certain conditions. These
    > include passing of time, movement of the cursor, speed of the
    > cursor, etc.
    >
    > All those methods are on MyDocument. For example:
    >
    > - (void)makeGraphic:(MVGraphic *)aGraphic changeStatusTo:(NSNumber
    > *)newStatus;
    > - (void)startTrial:(MVTrial *)trial;
    >
    > The reason I liked NSInvocations is that just before running the
    > experiment, I could wrap the selector and the arguments in an
    > invocation and when running the experiment, I could call -[invoke]
    > on both without knowing anything about which method is being called
    > and how many arguments it has.
    >
    > Assume I have an object with the arguments as instance variables.
    > When I'm using an IMP or -[performSelector] variants, I seem to need
    > something like this:
    >
    > switch (numberOfArguments) {
    > case 0:
    > // callBackAsImp has type void (*)(id, SEL)
    > callBackAsImp();
    > break;
    > case 1:
    > // callBackAsImp has type void (*)(id, SEL, id)
    > callBackAsImp(argument1);
    > break;
    > case 2:
    > // callBackAsImp has type void (*)(id, SEL, id, id)
    > callBackAsImp(argument1, argument2);
    > break;
    > }
    >
    > When I'm constructing the callBackAsImp, I'd need to have another
    > case statement to type it correctly. Does this make sense? Maybe I'm
    > missing the point here, but I haven't figured out how to get around
    > this yet.

    Why don't you just do something like I suggested in my previous e-mail:

    For every callback you have, write a method on MyDocument:

    For example:

    - (void)doStartTrial
    {
      [self startTrial:trial];  // Store trial as an instance variable of
    MyDocument
    }

    - (void)doMakeGraphic
    {
      [self makeGrahpic:graphic changeStatusTo:status]; // Likewise,
    store graphic and status as instance variables
    }

    Now just pass the selector round for the callback, so that when you
    want to trigger the callback, you just do:

    [self performSelector:selector];

    Kind regards,

    Chris
  • On Feb 25, 2008, at 6:03 PM, Chris Suter wrote:

    > Why don't you just do something like I suggested in my previous e-
    > mail:
    >
    > For every callback you have, write a method on MyDocument:
    >
    > For example:
    >
    > - (void)doStartTrial
    > {
    > [self startTrial:trial];  // Store trial as an instance variable of
    > MyDocument
    > }
    >
    > - (void)doMakeGraphic
    > {
    > [self makeGrahpic:graphic changeStatusTo:status]; // Likewise,
    > store graphic and status as instance variables
    > }
    >
    > Now just pass the selector round for the callback, so that when you
    > want to trigger the callback, you just do:
    >
    > [self performSelector:selector];

    I don't follow you here. Say I have 10 trials to run and I have trial
    as an instance variable of MyDocument. I then have to increment the
    instance variable before every call to doStartTrial. That seems more
    work than storing the argument as an instance variable of my MVAction
    class and making sure I've got the type of the function right.

    Worse, in the case of doMakeGraphic, I don't know what the instance
    variables should be until the call, since there are many calls to
    makeGraphic:changeStatusTo: and their order depends on a complex
    interplay of conditions that I have no way of predicting.

    I'm sorry if I'm missing your point. In the meantime, I'll stick to my
    NSInvocations...

    Thanks very much for your help!
    Hank

    Hank Heijink
    <hank.list...>
previous month february 2008 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 29    
Go to today