Tuesday, November 19, 2019

Direct Objective-C Properties

In a future compiler version:

A direct property specifier is added (@Property(direct) type name)

These attributes / specifiers cause the method to have no associated Objective-C metadata (for the property or the method itself), and the calling convention to be a direct C function call.

The symbol for the method has enforced hidden visibility and such direct calls are hence unreachable cross image. An explicit C function must be made if so desired to wrap them.

Via Tanner Bennett:

This feature tries it’s hardest to break everything that makes Objc great. KVC, KVO, swizzling, etc., even OOP because this is effectively final for objc. In case anyone was wondering why I’m disappointed in this addition.

I can only hope codebases adopt it sparingly.

Greg Parker:

Counterargument: the alternative to get this level of performance is to rewrite your method as a C function. That experience is awful in practice. It’s important to be able to tune dynamism vs performance without busy-work syntax changes.

It’s a great feature to have for this reason. But I hope it’s not overused.

Previously:

Update (2019-11-20): Jonathan Deutsch:

Counter-counter argument: this is needed rarely, and much more rarely than many will think it is needed.

In my experience, most developers jump at the opportunity for “performance” even when premature, not measured, and probably not going to be effective.

Part of Objective-C’s beauty was that it was simple. One used to be able to easily learn the language, read others’ code, and thus debug others’ code.

There’s no feature in a language of higher priority than this… definitely not performance.

Although one could argue that this feature makes the optimized code more readable.

See also: this thread.

Update (2019-11-27): Pierre Habouzit (thread):

The Obj-C dynamic dispatch comes with many costs, this is common “knowledge”. However the details of it are rarely known.

Beside the obvious cost of the h-lookup, it comes with 4 other kinds of costs:

- codegen size
- optimization barrier
- static metadate
- runtime metadata

[…]

When used on a typical Obj-C framework, it is easy to reduce your binary size by 5% or more. Using LTO will easily make this win even larger. It means in turn that the working-set of the processes is smaller, which gives you more space for things that are actually useful.

As such, you now start to see that despite the obvious initial reaction which is “holy s**t this is great for hot code”, the target audience is even more the long tail of rarely used monomorphic calls that are killing your binary size for very little added value.

Update (2019-12-17): Steve Troughton-Smith:

As far as things that make my life harder, inlining ObjC methods isn’t one of them. Getting human-readable disassembly out of Swift code is a nightmare, on the other hand. I’m far more concerned about Swift adoption increasing than improvements to ObjC

Louis Gerbarg:

The best part about that thread is that I am about 90% the StepStone ObjC compiler actually had a keyword with pretty much the same semantics as objc_direct. Unfortunately I can’t find any documentation for it.

Pierre Habouzit:

Not exactly. But close. The radar has it. It used to be - ([inline] __direct__ type)mySelector...;

See also: Mattt Thompson.

4 Comments RSS · Twitter

>> the alternative to get this level of performance is to rewrite your method as a C function. That experience is awful in practice.

Mixing in C was never awful for me, it was a featured advantage of the language. This property doesn't seem terrible, but it does feel unnecessary and might make code more difficult to read and reason about at a glance.

I thought Objective-C was essentially silently deprecated at this point and any additions were just to support Swift interoperability. I don't see what this feature adds in that regard.

> Although one could argue that this feature makes the optimized code more readable.

I think it boils down to a RISC vs. CISC-style argument. I'm firmly on the side that the fewer keywords/constructs in a language, the more understandable as a whole the code will be. I'll take 3 simple lines over one complicated line. Not that there isn't a place for new keywords, but it should be a very large win to include.

I'm also surprised to see any Objective-C enhancements that aren't there just for Swift, but this seems neat and tidy. There have been infrequent situations where I've written small C wrappers in .m files that might look something like this:

static NSInteger XXObjectCalculatedProperty( XXObject* self, NSInteger param1, NSInteger param2 ) 
{
     return (self->_ivar * param1) + ([self someMethod] * param2);
}

With this new "direct" attribute/property it will be nice to use normal Obj-C syntax, but with the performance of a pure C function, in those rare cases where you actually need this kind of optimization.

The only potential downside is that more and more code uses this direct attribute and prevents runtime patching. I know it's not pretty but there have been plenty of times I had to swizzle some Apple framework methods to fix a bug, add a feature, or just investigate the code's behavior. If "direct" is used too widely it will disable that invaluable tool. But that seems like a minor fear. I doubt that "direct" will be used often, since the Obj-C method calling machinery is almost never a relevant bottleneck.

A comment from the linked Twitter thread:

>> The objc_direct_members annotation seems to effectively implement truly-private methods for ObjC. Statically dispatched, not overridable. *I can see Apple using that a lot internally to avoid people calling private methods.*

There's the rationale I was missing. Instead of looking at this feature from a developer point of view, see it from Apple's: this allows Apple engineers to easily lock down ossified API surface just by adding a property decoration. Minimal rewriting/understanding required (essential for the parts of the codebase in minimal maintenance mode), and it can be applied progressively.

Leave a Comment