2

Transitioning to ARC on iOS.

I have an autoreleased NSString that I use to generate a UTF-8 representation, and rely on pool lifetime to keep the UTF-8 pointer alive:

char *GetStringBuffer(something)
{
    NSString *ns = [NSString stringWithSomething:something];
    return [ns UTF8String];
}

The nature of something is not important here.

Pre-ARC rules make sure the returned data pointer will stay valid for the lifetime of current autorelease pool. Crucially, I don't carry the NSString pointer around.

Now, under ARC, won't the string be released when the function returns? I don't think ARC will consider a char * to a structure deep inside an NSString a strong reference, especially seeing that it's not explicitly freed ever.

What's the best ARC idiom here?

Seva Alekseyev
  • 59,826
  • 25
  • 160
  • 281
  • 1
    Are you having an issue with that code under ARC? `ns` is still autoreleased under ARC. – rmaddy Sep 17 '15 at 16:57
  • Yes, but the question is *when*? – Seva Alekseyev Sep 17 '15 at 16:59
  • 1
    ARC doesn't change how autorelease works. Nothing changes in that regard. All ARC does is implicitly add `release` and `retain` when needed. It sounds like you are worrying about a problem that doesn't actually exist or you haven't encountered. – rmaddy Sep 17 '15 at 17:01
  • In other words, your code is fine. – rmaddy Sep 17 '15 at 17:01
  • So the Cocoa objects that are allocated with [objectWithXXX] still get the same lifetime as before? – Seva Alekseyev Sep 17 '15 at 17:05
  • Sure. Why would ARC change that? – rmaddy Sep 17 '15 at 17:08
  • @rmaddy `ns` is not returned so there is no need for ARC to autorelease `ns`. `ns` will be probably released immediately on method exit. – Sulthan Sep 17 '15 at 17:11
  • No live strong references=time to kill, no? How can I test that? – Seva Alekseyev Sep 17 '15 at 17:12
  • @Sulthan But `ns` is an `autoreleased` object created from `NSString stringWithFormat`. So just like with MRC, ARC will autorelease it the same way - through the current autorelease pool. – rmaddy Sep 17 '15 at 17:13
  • @rmaddy Oh, you are right... Still I would consider code relying on such behavior a bit unsafe. – Sulthan Sep 17 '15 at 17:15
  • @rmaddy, I've personally seen code like the above crash under ARC. `ns` is an automatic variable and does not have precise lifetime semantics (see http://clang.llvm.org/docs/AutomaticReferenceCounting.html#precise-lifetime-semantics), so ARC is free to destroy it before the `char*` is consumed. This is dangerous code. See Mark Dalrymple's discussion here for a specific example: https://www.bignerdranch.com/blog/arc-gotcha-unexpectedly-short-lifetimes/ – Rob Napier Sep 17 '15 at 18:02
  • @RobNapier I agree. My main point is that the behavior under ARC for this situation shouldn't be any different than it was under MRC. – rmaddy Sep 17 '15 at 18:03
  • But it often will be. Mark's code in the linked article would traditionally have been fine under MRC, but crashes under ARC. I've experienced exactly the same thing. ARC doesn't make the same promise about autorelease pools that MRC (at least implicitly) makes. Taking inner pointers (like `UTF8String`) is exactly the kind of thing that blows up. – Rob Napier Sep 17 '15 at 18:05
  • Have to take some of that back. While *ARC* doesn't promise to save this till the autorelease pool pops, `UTF8String` *does* promise it by being marked `objc_returns_inner_pointer`. I'd forgotten that they'd added that at some point to save people from exactly this. See answer below. – Rob Napier Sep 17 '15 at 18:26

3 Answers3

3

If you want to guarantee that the return value of UTF8String is valid until the current autorelease pool is drained, you have two options:

  1. Define GetStringBuffer in a file that is compiled with ARC disabled. If stringWithSomething: follows convention, it must return an autoreleased NSString to a non-ARC caller. If it doesn't (e.g. it acts like -[NSArray objectAtIndex:]), you can explicitly retain and autorelease it.

  2. Use toll-free bridging and CFAutorelease:

    char *GetStringBuffer(something) {
        NSString *ns = [NSString stringWithSomething:something];
        CFAutorelease(CFBridgingRetain(ns));
        return [ns UTF8String];
    }
    
Community
  • 1
  • 1
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • This answer is correct, and my previous answer was misleading. There is no promise that the returned pointer from `UTF8String` is valid past the return statement. The ARC docs specifically say ["the implementation never need account for uses after a return"](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#interior-pointers). See also https://twitter.com/jckarter/status/647807319649075200. I'm deleting my answer to avoid confusion. – Rob Napier Sep 26 '15 at 16:22
2

I thought the right way to do this was using __autoreleasing:

https://releases.llvm.org/11.1.0/tools/clang/docs/AutomaticReferenceCounting.html#storage-duration-of-autoreleasing-objects

E.g.

char *GetStringBuffer(something) NS_RETURNS_INNER_POINTER {
    __autoreleasing NSString * ns = [NSString stringWithSomething:something];

    return [ns UTF8String];
}
E000R
  • 21
  • 1
0

(I can't delete this because it's accepted, but this answer is incorrect. See Rob Mayoff's answer, and the comments on that answer for an explanation. There is no promise that this pointer is valid past the return statement.)

Rewriting my whole answer. Had to dig and dig, but I believe this is surprisingly safe today (due to improvements in ARC since I had my crashes; I knew something like that was rolling around in the back of my head). Here's why:

@property (readonly) __strong const char *UTF8String NS_RETURNS_INNER_POINTER;  // Convenience to return null-terminated UTF8 representation

UTF8String is marked NS_RETURNS_INNER_POINTER, which is really objc_returns_inner_pointer. Calling that method:

the object’s lifetime will be extended until at least the earliest of:

  • the last use of the returned pointer, or any pointer derived from it, in the calling function or
  • the autorelease pool is restored to a previous state.

Which is pretty much what you wanted it to do.

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Any iOS version limits on that? – Seva Alekseyev Sep 17 '15 at 18:37
  • The problem here is that ARC is allowed to release the `NSString` as soon as his `GetStringBuffer` function returns, because that is the first bullet point you quoted. – rob mayoff Sep 17 '15 at 18:40
  • @SevaAlekseyev I think it's at least back to iOS 6. It's not recent, but you'd have to look through release notes to see exactly. – Rob Napier Sep 17 '15 at 18:40
  • @robmayoff The question is whether returning the pointer counts as creating a derived pointer. I assume it does (though I do not have proof of that position). – Rob Napier Sep 17 '15 at 18:53
  • Verified that this was added in iOS 6 BTW. https://developer.apple.com/library/prerelease/ios/releasenotes/General/iOS60APIDiffs/index.html#//apple_ref/doc/uid/TP40011959 – Rob Napier Sep 17 '15 at 18:53
  • “the last use of the returned pointer, or any pointer derived from it, **in the calling function**” The “calling function” is `GetStringBuffer`. Even if `return` creates a “derived pointer”, any use of that “derived pointer” is outside of the calling function. – rob mayoff Sep 17 '15 at 19:12
  • So returning the pointer doesn't hint the compiler that it might be used by the caller? :) – Seva Alekseyev Sep 17 '15 at 19:34
  • The only thing it could actually do at that point is force it onto the autorelease pool (it doesn't know who the caller is). My bet is that that's exactly what happens. But @robmayoff has a reasonable point that it's not explicitly promised. – Rob Napier Sep 17 '15 at 20:24
  • If `UTF8String` were compiled with ARC (which it probably isn't, since it's old code), and the caller (`GetStringBuffer`) were compiled with ARC, then `UTF8String` can (by way of `objc_autoreleaseReturnValue`) hand off ownership of the returned object to `GetStringBuffer` (by way of `objc_retainAutoreleasedReturnValue`), instead of putting it in the autorelease pool. Then `GetStringBuffer` can release the string before returning. See [this](https://www.bignerdranch.com/blog/arc-gotcha-unexpectedly-short-lifetimes/) and [this](http://rentzsch.tumblr.com/post/75082194868/arcs-fast-autorelease). – rob mayoff Sep 17 '15 at 20:30
  • @robmayoff Yeah; I know the horror stories (I had them myself; I linked the BNR article up above :D). But objc_returns_inner_pointer adjusts that behavior. Mark's problem wouldn't happen today because the `CGColor` property is now marked `NS_RETURNS_INNER_POINTER`. That's why they added that inner pointer attribute, because people kept getting burned by surprising ARC releases. The inner pointer attribute extends the lifetime based on the returned (and "derived") pointers. The only question is whether "returned pointer" is a "derived pointer." I think it is, but might be wrong. – Rob Napier Sep 17 '15 at 20:43
  • Note the "*at least* the earliest of..." They can conform to this spec by throwing it on the autorelease pool if the pointer escapes, and my reading of it is that's what they likely do. (But your answer is absolutely safer because there's no ambiguity at all. No argument there for OS X 10.9 and above, which is always above what I can use :D The question is iOS of course, and that goes back to 7, which is darn good.) – Rob Napier Sep 17 '15 at 20:45