Does CTLineRef own attributed string?

1.1k views Asked by At

I'm just getting started with programming GUIs on apple (so far the framework seems fine, but I find the documentation far less informative than others...Qt, .Net, java, etc).

One of the issues I have had is understanding who owns what. For example, if I call CTLineRefCreateWithAttributedString, does the resulting CTLineRef own the attributed string? What if the attributed string is mutable, and I change it? Will this mess up the CTLineRef?

The documentation has been unenlightening.

The reference for CTLineRef provides no information on the subject: https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CTLineRef/Reference/reference.html

Some examples don't release the string, which I take as an indicator that it is owned: https://developer.apple.com/library/mac/#documentation/StringsTextFonts/Conceptual/CoreText_Programming/Operations/Operations.html

Some examples do release the string, which would suggest it is not: https://developer.apple.com/library/mac/#samplecode/CoreAnimationText/Listings/VectorTextLayer_m.html

(this one isn't apple, but he seems more informed them I) http://www.cocoanetics.com/2011/01/befriending-core-text/

So is the string copied or not? If I use a CFMutableAttributedStringRef can I change that or not (I assume I would have to create a new CTLineRef afterwards)?

This is a specific example, but this is question that I have come up against in innumerable places. Any help would be very much appreciated. I feel that there must be some rules that govern these things, but I haven't the slightest idea what those rules are or where I can find them.

3

There are 3 answers

4
rob mayoff On

To answer your question, we can try looking at the string's reference count before and after creating the CTLine. We can also try printing the line's description before and after changing the string.

CFMutableAttributedStringRef mas = CFAttributedStringCreateMutable(NULL, 0);
CFAttributedStringReplaceString(mas, CFRangeMake(0, 0), CFSTR("world"));
CTLineRef line = CTLineCreateWithAttributedString(mas);
NSLog(@"mas count = %ld", CFGetRetainCount(mas));
NSLog(@"line before change = %@", line);
CFAttributedStringReplaceString(mas, CFRangeMake(0, 0), CFSTR("hello "));
NSLog(@"line after change = %@", line);

It's often fruitless to look at an object's retain count, but in this case it's informative:

2012-08-03 12:11:10.717 coretext[44780:f803] count before creating line = 1
2012-08-03 12:11:10.720 coretext[44780:f803] count after creating line = 1

Since the retain count is 1 before and after, and I own a reference (because CFAttributedStringCreateMutable gives me an owning reference), I know that I am the sole owner of the string, before and after I create the CTLine. So CTLine doesn't retain the string. It's extremely unlikely that it keeps a reference to the string without retaining it.

Here's the line's description before changing the string:

2012-08-03 12:11:10.721 coretext[44780:f803] line = CTLine: run count = 1, string range = (0, 5), width = 28.6758, A/D/L = 9.24023/2.75977/0, glyph count = 5
{
    CTRun: string range = (0, 5), characters = { 0x0077, 0x006f, 0x0072, 0x006c, 0x0064 }, attributes =
<CFBasicHash 0x6d69ce0 [0x1227b38]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0xab1b0 [0x1227b38]>{contents = "NSFont"} = CTFont <name: Helvetica, size: 12.000000, matrix: 0x0>
CTFontDescriptor <attributes: <CFBasicHash 0xd345ed0 [0x1227b38]>{type = mutable dict, count = 1,
entries =>
    1 : <CFString 0xabbd0 [0x1227b38]>{contents = "NSFontNameAttribute"} = <CFString 0x6d69720 [0x1227b38]>{contents = "Helvetica"}
}
>
}
}

I notice that the description doesn't include the string, but does include an array of characters. So the line probably doesn't keep a copy of the string either; it parses the string to create its own private representation.

Here's the line's description after changing the string:

2012-08-03 12:11:10.722 coretext[44780:f803] line = CTLine: run count = 1, string range = (0, 5), width = 28.6758, A/D/L = 9.24023/2.75977/0, glyph count = 5
{
    CTRun: string range = (0, 5), characters = { 0x0077, 0x006f, 0x0072, 0x006c, 0x0064 }, attributes =
<CFBasicHash 0x6d69ce0 [0x1227b38]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0xab1b0 [0x1227b38]>{contents = "NSFont"} = CTFont <name: Helvetica, size: 12.000000, matrix: 0x0>
CTFontDescriptor <attributes: <CFBasicHash 0xd345ed0 [0x1227b38]>{type = mutable dict, count = 1,
entries =>
    1 : <CFString 0xabbd0 [0x1227b38]>{contents = "NSFontNameAttribute"} = <CFString 0x6d69720 [0x1227b38]>{contents = "Helvetica"}
}
>
}
}

We can see that the line hasn't changed its glyph count or its characters array. From this we can conclude that the line doesn't change when you change the string. You could test further by actually drawing the line before and after changing the string. I leave that as an exercise for the reader.

0
hooleyhoop On

The MutableString thing should not be an issue - the CTLine docs clearly state that is immutable. So there is no way to change it after it has been created - that is all you need to know. It doesn't matter if it copies, retains, or whatever, or what you do to the string you created the CTLine with - it is a frozen object.

Also, just to support Matt's excellent answer - the whole point of retain/release is to get away from the terrible idea that an object's must be 'owned'. It is not helpful to think in this way. For example we never have the responsibility to free Objects, an object's lifespan is unknowable. Retain/release is more like a simple garbage collector.

You just have to follow the simple rules, which are best learnt straight from the source at Apple, where they are well documented. These simple rules are always followed.

So, no, you won't see specific memory management guidelines in the documentation for each function. It really isn't needed. If it hasn't clicked yet you need to go back to basics.

3
Matt Wilding On

The governing rules that you seek are found in the Core Foundation Memory Management Guide. The main point to take away is that you actually don't need to know if the CTLineRef owns the attributed string. Any object can have multiple owners. All you need to worry about is whether or not you own string, and where the most appropriate place for you to relinquish ownership is. If you're done with it after you create the CTLineRef, then feel free to release it.

Consider the rest an implementation detail of CTLineRef. If CTLineRef is following the rules laid out in that document (which it does) and needs to keep the attributed string around, it will have retained it internally. It's also possible that it makes a copy internally (this is likely), and therefore no longer cares about the original. Maybe it sends the string to a server on Mars and queries it every time it needs it later (less likely). The important point is that, regardless of what it does, you can safely release the string if you no longer need it.

As for the mutability and copying behavior, that's a little more fuzzy. You're right that the documentation isn't explicit about the behavior. The documentation is explicit that the object is immutable. That means it's all but guaranteed that it does in fact copy the input string (or parse its contents into something else). It's commonly understood best practice that objects should always make copies of input strings for the exact reason you mention - that the implementation can't know if the supplied string is actually mutable. In order for the implementation to remain robust, it needs to make sure that its internal state can't be changed from the outside world. The only way to make that guarantee is to copy the string. This is true even of mutable classes, which CTLineRef is not.