Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does -[NSInvocation retainArguments] copy blocks?

NSInvocation's -retainArguments method is useful for when you don't run the NSInvocation immediately, but do it later; it retains the object arguments so they remain valid during this time.

As we all know, block arguments should be copied instead of retained. My question is, does -retainArguments know to copy instead of retain an argument when it's of block type? The documentation does not indicate that it does, but it seems like an easy and sensible thing to do.

Update: The behavior seems to have changed in iOS 7. I just tested this, and in iOS 6.1 and before, -retainArguments didn't copy parameters of block type. In iOS 7 and later, -retainArguments does copy parameters of block type. The documentation of -retainArguments has been updated to say that it copies blocks, but it does not say when the behavior changed (which is really dangerous for people who support older OS's).

like image 555
user102008 Avatar asked Apr 18 '13 00:04

user102008


People also ask

What is NSInvocation?

NSInvocation objects are used to store and forward messages between objects and between applications, primarily by NSTimer objects and the distributed objects system. An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value.

How do you use NSInvocation?

You open up a new email ( NSInvocation object), fill in the address of the person (object) who you want to send it to, type in a message for the recipient (specify a selector and arguments), and then click "send" (call invoke ).


2 Answers

It's certainly supposed to (albeit I haven't test it myself). According to the documentation:

retainArguments

If the receiver hasn’t already done so, retains the target and all object arguments of the receiver and copies all of its C-string arguments and blocks.

  • (void)retainArguments

Discussion

Before this method is invoked, argumentsRetained returns NO; after, it returns YES.

For efficiency, newly created NSInvocation objects don’t retain or copy their arguments, nor do they retain their targets, copy C strings, or copy any associated blocks. You should instruct an NSInvocation object to retain its arguments if you intend to cache it, because the arguments may otherwise be released before the invocation is invoked. NSTimer objects always instruct their invocations to retain their arguments, for example, because there’s usually a delay before a timer fires.

like image 120
Mr. T Avatar answered Sep 30 '22 06:09

Mr. T


No.

Image if the answer is yes, where NSInvocation is smart enough to copy block, it should do something like this:

for (/*every arguments*/) {
    if (/*arg is object. i.e. @encode(arg) is '@'*/) {
        if ([arg isKindOfClss:[NSBlock class]]) {
            arg = [arg copy]; // copy block
        } else {
            [arg retain];
        }
    }
}

the problem is that arg is modified while copying the block, which should not happen because this means call retainArguments may change the arguments in the NSInvocation. this will break many assumptions that already made. (i.e. arguments get from NSInvocation should be same as arguments used to create the NSInvocation)


Update

just did the test to conform the answer is NO, but my previous point was incorrect though...

@interface Test : NSObject

@end

@implementation Test

- (void)testMethodWithBlock:(void (^)(void))block obj:(id)obj cstr:(const char *)cstr {
    NSLog(@"%p %p %p %@", block, obj, cstr, [block class]);
}

@end

@implementation testTests

- (void)test1 {
    __block int dummy;
    Test *t = [[Test alloc] init];
    NSMethodSignature *ms = [t methodSignatureForSelector:@selector(testMethodWithBlock:obj:cstr:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:ms];
    void (^block)(void) = ^ {
        dummy++;    // stop this become global block
    };
    id obj = @"object";
    char *cstr = malloc(5);
    strcpy(cstr, "cstr");


    NSLog(@"%@", [ms debugDescription]);

    NSLog(@"%p %p %p %@", block, obj, cstr, [block class]);

    [invocation setSelector:@selector(testMethodWithBlock:obj:cstr:)];
    [invocation setArgument:&block atIndex:2];
    [invocation setArgument:&obj atIndex:3];
    [invocation setArgument:&cstr atIndex:4];

    [invocation invokeWithTarget:t];

    [invocation retainArguments];

    [invocation invokeWithTarget:t];

    free(cstr);
}

@end

output, ARC disabled (and crashed):

2013-04-18 19:49:27.616 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.617 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.618 test[94555:c07] 0xbfffe120 0x70d2254 0x736a810 __NSStackBlock__

ARC enabled:

2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.980 test[95323:c07] 0x7101e10 0x70d2268 0xe0c1310 __NSMallocBlock__

as you can see, c string are copied by retainArguments but not blocks. but with ARC enabled, the problem should go away because ARC copied it for you at some point.

like image 35
Bryan Chen Avatar answered Sep 30 '22 06:09

Bryan Chen