Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a pointer to an object declared outside a block, inside the block

I'm trying to use the pointer of an object declared outside an Objective-C block, inside the block itself. For example:

NSError* error = nil;
[self invokeAsync:^id{
    return [self doSomething:&error];
}];

I get a compiler error on the third line telling me:

Sending 'NSError *const__strong *' to parameter of type 'NSError *__autoreleasing *' changes retain/release properties of pointer

Why is that?

like image 796
Mark Avatar asked Oct 31 '13 06:10

Mark


2 Answers

The compiler message is confusing, but is telling you that you have a type mismatch.

But, doesn't matter, because that code makes no sense.

An asynchronous invocation cannot set state in the calling thread's stack. I.e. there is no way that error can be set to a meaningful value.

That is, the method invokeAsync: will return before the work block is executed. Thus, there is no way to return anything meaningful from invokeAsAsync: to indicate the success/failure of the execution of the block.


If you want to invoke something asynchronously with an error, you'll need a callback:

[self invokeAsync:^id{
    NSError *e;
    if ([self doSomething:&e])
        [self errorHappened:e];
    else
        [self asyncThingyDone];
}];
like image 181
bbum Avatar answered Oct 21 '22 18:10

bbum


There are two issues here. The first is the timing issue which is already pointed out by @bbum.

The other may be what you are asking for, i.e., why the compiler gives such an error. Also as @bbum said, "The compiler message is confusing". In order to decouple the second issue from the first one, let's assume your call is invokeSyncAndWait: instead of invokeAsync:. Now the timing issue is gone we can focus on the second issue:

NSError* error = nil;
[self invokeSyncAndWait:^id{
    return [self doSomething:&error];
}];

The error variable captured in the block is just a by-value copy, not a real reference:

Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.

Because you don't have a real reference to the variable error in the block, you can not take the address of it. That's why the compiler refuses to compile your code.

To get a by-reference variable, you should use __block:

Variables local to the enclosing lexical scope declared with the __block storage modifier are provided by reference and so are mutable. Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in “The __block Storage Type.”

So the working code is:

__block NSError* error = nil;
[self invokeSyncAndWait:^id{
    return [self doSomething:&error];
}];

It is still perilous code though:

Thus, the address of a __block variable can change over time.

I just explained the compiling issue.

like image 3
an0 Avatar answered Oct 21 '22 18:10

an0