7

Is it possible (and if so, safe) to create/use a block which takes a double pointer as an argument?

For instance:

- (void)methodWithBlock:(void (^)(NSError **error))block;


Additional context, research, and questions:

  • I'm using ARC.
  • When I declare the method above and attempt to call it, XCode autocompletes my method invocation as follows: [self methodWithBlock:^(NSError *__autoreleasing *error) {}]; What does __autoreleasing mean here and why is it being added? I presume it has something to do with ARC.
  • If this is possible and safe, can the pointer still be dereferenced in the block as it would be anywhere else?
  • In general, what are the important differences between doing what I'm describing, and simply passing a double pointer as a method parameter (e.g. - (void)methodWithDoublePointer:(NSError **)error;)? What special considerations, if any, should be taken into account (again assuming this is possible at all)?
George WS
  • 3,903
  • 5
  • 27
  • 39
  • You may want to Google/search: `- (void) foo: (NSError * __autoreleasing *) error { }` – verec Oct 21 '13 at 21:24
  • 2
    @verec Not everyone is using ARC... – Macmade Oct 21 '13 at 21:25
  • 1
    But most everyone do! :-) – verec Oct 21 '13 at 21:25
  • 2
    Yep... Let's just hide one of the most important part in computer programming, so it feels like magic, like you shouldn't care, like you shouldn't worry about... ^^ – Macmade Oct 21 '13 at 21:32
  • Not too sure what `NSError * __autoreleasing *` is hiding ... [talking of ARC as a leaky abstraction would be more to the point] – verec Oct 21 '13 at 21:34
  • 1
    @RobNapier [Not sure if that's the case...](https://devforums.apple.com/thread/208688) –  Oct 21 '13 at 22:02
  • 2
    @H2CO3, it's worth reading deeper into the thread. Rincewind sums it up well here: https://devforums.apple.com/message/906963#906963. ARC solves the vast majority of problems better than hand coding, but every so often, you need adjust for it. It's not that it hides an important part of programming any more than the compiler does. I can hand-code some loops in NEON better than clang (and I've done so). That doesn't mean I should compile with -O0 so "I'm in full control." That would be silly. So is turning off ARC in the vast majority of cases. – Rob Napier Oct 21 '13 at 23:37
  • 2
    gparker also points out one of the oft-overlooked differences with ARC (https://devforums.apple.com/message/904969#904969). ARC focuses first on safety; then on performance. (No program is as slow as one that crashes.) ARC assumes that you may do odd things in your methods and protects against that with some extra retains that people don't always include by hand. In some tight loops, that can be a problem (I've also seen plenty of cases where the app crashes because people didn't put them in). For high performance loops, I typically go C, but that doesn't mean I avoid ObjC most of the time. – Rob Napier Oct 21 '13 at 23:48
  • @RobNapier My point is not that "ARC is always slow", rather that "it can be sometimes better to turn off ARC". Just like you hand-code your NEON instructions for a reason. This was one exceptional case where ARC was a bad choice. Your "The rest should" comment suggests that no-one ever should turn off ARC, which has just been proven wrong... –  Oct 22 '13 at 08:19
  • 1
    @RobNapier Furthermore, couldn't it be a personal preference as well? Some like ARC, some don't. Personally, I don't like it (and I don't use it). The reason is: it was supposed to solve a problem (i. e. overreleasing and memory leaks), but it did that by introducing other problems. Getting ARC code right with all those `__bridge_transfer` and `__autoreleasing` modifiers is equally hard -- it's just as hard as properly balancing `retain`s and `releases`, but the ARC-enabled code looks ugly in addition (and readability is something that heavily affects code correctness). I've been programming –  Oct 22 '13 at 08:23
  • @RobNapier in Objective-C for 3 years now, and I can assert quite confidently that I haven't made a single memory management mistake during the last year (at least as far as Instruments and the debugger is concerned). –  Oct 22 '13 at 08:23
  • I've been developing Cocoa since 10.3 & also very seldom have manual memory management errors, mostly because I consistently use accessors. I still code MRC on one project because it supports 32-bit 10.6. But my opinion hasn't changed from when I wrote this: http://stackoverflow.com/questions/8760431/to-arc-or-not-to-arc-what-are-the-pros-and-cons/8760820#8760820 ARC continues to be one of the most amazing additions I've seen to any language. Even in the very rare cases when ARC is a problem, I have found it better to adjust my algorithm or switch to C rather than go back to manual retains. – Rob Napier Oct 22 '13 at 12:32
  • @RobNapier Indeed. To expand on this: ARC is a good tool but perhaps Apple haven't got the implementation entirely right. I guess what I see as errors in ARC is just the top of the iceberg. In fact, ARC is still the best automatic memory management technique, and I've chosen to do seomthing like ARC in my scripting language, instead of the usual garbage collection approach. –  Oct 22 '13 at 20:39

3 Answers3

4

yes, pointers are always just pointers. you only need to make sure you dereference it before sending it a message (assuming objc object).

Also be aware that the pointer may be nil. always check it before trying to dereference it or what have you.

As @verec mentioned if you are using ARC you should declare the parameter as __autoreleasing

according to the docs

__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.

remember id is a pointer to an object so that is saying object**

there is no difference between passing pointers to pointers to methods or blocks.

Brad Allred
  • 7,323
  • 1
  • 30
  • 49
  • 3
    Actually you don't have to specify `__autoreleasing`, the compiler assumes that by default in this case. – Martin R Oct 21 '13 at 21:34
  • 1
    even if it does assume it the NSError* used to pass the address should be declared __autoreleasing according to the example from the [apple docs](https://developer.apple.com/library/mac/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html) – Brad Allred Oct 21 '13 at 21:50
2

The answers are both Yes & No...

At a base level passing pointers to pointers to blocks is no different than passing them to methods; and, with the usual proviso that your pointers must be valid, is perfectly OK.

However that __autoreleasing is very significant here and is tied up with ARC and pass-by-writeback. Whether using the block will work as expected will be dependent on context as the compiler often uses hidden variables when passing parameters of type NSError * __autoreleasing * as part of the pass-by-writeback implementation.

If pass-by-writeback is not what you need, or is unsuitable, you may wish to declare you block as taking a different type, such as NSError * __strong *. Read this answer which explains what happens under the hood, that should help you decide whether in your context the block declaration is good.

In summary (a) declaring the block is fine, but (b) you need to understand how it may be called and may need to change the signature.

Community
  • 1
  • 1
CRD
  • 52,522
  • 5
  • 70
  • 86
0

warning: untested

For the sake of argument, let's start with:

typedef NSError * NSErrorPtr ;

- (void) foo: (NSErrorPtr *) errPtr {
    *errorPtr = [NSError new] ;
}

errPtr isn't declared either __weak nor __strong.

So, according to ARC, even though its contents is allocated within foo the responsibility for releasing it has to reside somewhere.

Where?

Note that this not a property of double pointers per se. But of your pattern of allocation.

consider:

int ** arrayOfarrayOfInts = {
    {1, 2, 3, 4}    
,   {5, 6, 7, 8}
} ;

- (void) incrementElement: (int **) elem {
    ++(**elem) ;
}

- (void) bumpFirstColByOne {
    for (int i = 0 ; i < 2 ; ++ i) {
       int * arrayOfInt = arrayOfarrayOfInts[i] ;
       int ** second = &arrayOfInt[0] ;
       [self incrementElement: second] ;
    }
}

No __autoreleasing needed here because no allocation is taking place ...

verec
  • 5,224
  • 5
  • 33
  • 40
  • 2
    That `int **arrayOfArrayOfInts` example is incorrect. Arrays are not pointers, this doesn't compile. Don't ignite/feed false beliefs. –  Oct 21 '13 at 21:55
  • 2
    you are also incorrect that `errPtr` is neither weak nor strong. by default undeclared "strength" is strong. – Brad Allred Oct 21 '13 at 21:57
  • It _does_ compile [admittedly with _scary_ warnings], but it does _not_ run. Hence why I prefaced it with: _warning: untested_. You're welcome to edit and fix it, assuming you _do_ see the point being made, though ... – verec Oct 21 '13 at 22:00
  • 2
    @verec So did you compile it? `int **aray = { some array intializer }` does not compile because of the incompatible types. –  Oct 21 '13 at 22:01
  • 2
    @verec I'm not ignoring your main point. The answer is good otherwise, but I don't like when one spreads common (and terrible) mischiefs. Also, I can very well differentiate between a warning and an error. This won't cause a warning, but an error. [Like this](http://ideone.com/oooHzS). –  Oct 21 '13 at 22:10