In the comments on yesterdays initial post in a series following the experiences of porting an Objective-C sample to XE2, a number of people have asked why I didn’t use the TCFString record type in System.Mac.CFUtils to get the CFStringRef references that I required. The reason is embarrassingly simple.
I didn’t know about it at the time. 🙂
But now that I do, I am now curious as to how anyone does stumble across it. Even more curious as to the experiences of people have found if/when they use it.
First of all, there is no mention of it’s contents or relevance in the XE2 documentation. There is not even a passing reference to it in the help page specifically about “Mac OS X Application Development” or any of the linked articles discussing “Considerations for Cross-Platform Applications“.
In fact, the only mention of it is in the list of unit names and scopes.
However, being now aware of it I took a look and quickly concluded that this unit was not a serious attempt to expose these CoreFoundation types to XE2 developers – more likely it looks like a unit that was created to prop up the FireMonkey developers (i.e. the creators, not consumers, of the FireMonkey framework) but not intended for general purpose use.
Not only are the encapsulations incomplete they are also dangerously naive – there is a complete lack of error checking. They are essentially the XE2 equivalent of StrPCopy() (and when was the last time you used that?)
Even if that were not the case, this sort of thing has a really, really bad smell about it to me:
var s: String; str: CFStringRef; begin : s := 'The quick brown fox...'; str := TCFString.Create(s).Value; : end;
Didn’t we just leak a TCFString ?
No, we didn’t, because TCFString is a record type, not a class. It’s just that to use this “type” we are forced to write code that smells really, really bad and is visually indistinguishable from flat out wrong code.
However, inside that TCFString record is a CFStringRef value, and on the CoreFoundation side of things we do (I think) need to call CFRelease() on that reference at some point. Curiously as far as I can tell this won’t happen when the TCFString record is disposed and one of the omissions in the implementation of this “encapsulation” is the absence of any explicit “Release” method which you might expect to exist. You can indicate that you want the ref to be Release’d as part of conversion to a String or a Char, but that’s it.
Would You Expect Your Dog to Eat This ?
Perhaps if we look at the way this type is used within the Delphi RTL code itself, we will get a better idea of how it should be used. Without documentation “Use the Source” has always been a great way to learn such things, thanks to the availability of the RTL/VCL source code.
(Apologies to users of the Starter Editions… you are expected to get “Started” without even this assistance)
So let’s find a few usages of TCFString in the RTL/FMX code to look at …
Oh.
There’s actually only one area where this is used at all: System.DateUtils
And it isn’t used to convert from a Delphi String to a CFStringRef but rather the exact opposite – to capture a CFStringRef temporarily for the purposes of converting to a Delphi String.
Well, maybe FireMonkey doesn’t need to do this string-type shuffle at all ? Maybe this TCFString type is provided as a convenience for the rest of us but wasn’t actually needed in FMX after all.
Well, no.
If we search for calls to CFRelease() (notably missing from TCFString remember) in the FMX source, then we find numerous calls involving strings, and those strings are CFStringRef values that are initialised using the CoreFoundation CFStringCreate… factory functions.
The Real Question …
The question perhaps shouldn’t be why don’t *I* use TCFString for such things, but rather why don’t Embarcadero use it themselves ?
I at least have the excuse that Embarcadero didn’t tell me about it or show me how to use it. They have no such excuse.
Could You Pass the Smelling Salts Please … ?
But if the smell so far is bad, it’s about to get even worse, because in searching for “TCFString” we find that there are not one but TWO TCFString record types with this name.
There is the one in System.Mac.CFUtils but there is also one in System.SysUtils. This one is a private implementation detail to this unit and provides only a subset of the “real” TCFString.
This is perhaps a clumsy attempt to avoid a circular unit reference or some awkward name-spacing issue with the units as the SysUtils unit would otherwise have to use the Mac.CFUtils unit which itself already uses the SysUtils unit ?
I suspect that the developers working on SysUtils fell foul of the admonishment in the source to avoid adding any units to the implementation uses clause and so fell back on good old “Copy/Paste” code re-use.
Ah, progress in robust software development practices. Who needs it ?
Ironically, if instead of creating a new, undocumented unit the TCFString type and it’s brethren had been put in the unit that would make most sense (to me), i.e. MacAPI.CoreFoundation, there would have been no need for such bad odours as SysUtils already uses this unit.
Are You Eating the Food that Embarcadero Can’t Bring Themselves to Eat?
So, I am curious – is anyone using TCFString for anything other than converting CFStringRef values to Delphi strings, and if so, how are you finding it ?
Thanks Jolyon. I like this approach to learning Delphi on the Mac and your stream of consciousness writing style.
Go to http://qc.embarcadero.com/
Click “Delphi”: http://qc.embarcadero.com/wc/qcmain.aspx?p=10
Amongst “Top 10 Voted For Delphi” there is a report #21729: Record Operator Overloading: Please implement “Initialize” and “Finalize” operators
Almost 7 years in the top 10, and we are still not yet there. I can hardly remember myself writing the first comment when QC was on qc.borland.com domain.
Happily enough, with some craploads of code, the issue can be beautifully workarounded:
1. Wrap CFStringRef inside either interface or custom Variant type. Custom Variant is better because one level of indirection less => several memory manager invokations less => faster
2. Wrap Variant inside private field of record
3. Supply record with all the proxy methods CFStringRef has. Variant field will be varEmpty by default, and all we can do is to either auto-initialize empty Variant with empty CFStringRef inside every proxy method or treat empty Variant as empty string.
4. Define overloaded operators including explicit type conversions.
5. Happy end!