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...>


