[Pitch] Introduce @objc_direct Attribute

Hi everyone, we would like to propose a new attribute for Swift-Objc interoperability, the @objc_direct attribute, in Swift language.

When @objc attribute is marked on Swift methods and properties, it exposes the methods and properties to ObjC code. The Swift compiler generates ObjC metadata that are required by ObjC runtime to support dynamic dispatch of these methods and property accessors. But sometimes dynamic dispatch is not really necessary for certain methods and properties. In ObjC code, the attribute__((objc_direct)) and direct attributes can be annotated on those to bypass the generation of associated ObjC metadata and convert the calling convention to be a direct C function call, for size saving of applications and potential runtime performance improvement as well.

Therefore, we propose an @objc_direct attribute in Swift language, to indicate a method or property should be generated as an objc_direct ObjC method or a direct ObjC property when they are exposed to ObjC code. Specifically, the swift compiler should not emit associated ObjC metadata for them, and the invocations of the method and property accessors should be generated as C-style direct calls.
Similar to attribute__((objc_direct_members)), we also propose @objcDirectMembers on Swift class, denoting all the members of a class should be direct.

We appreciate any feedback or questions about this pitch!

Sharon @mren @NuriAmari

cc @Joe_Groff @John_McCall @Mike_Ash

7 Likes

First thought, with more possible nuance to come later: could we infer this from a combination of @objc and final? (And disable the inference under @IBAction or dynamic or anything else that suggests use of the ObjC runtime.)

Possible drawback: __attribute__((objc_direct)) is not ABI-compatible with non-objc_direct, so this would break compatibility with older ObjC compilers. (I don't remember how long objc_direct has been available in Clang.)

EDIT: On second thought, that would break any uses of #selector and key-value coding, so maybe not.

3 Likes

Due to lack of technical detail, it’s very unclear what you are proposing here. At an ABI level, there’s no difference between an __attribute__((objc_direct)) method and a non-@objc Swift method except for mangling. Are you proposing that @objc_direct method use ObjC mangling? That won’t do much without also somehow making the declaration visible to ObjC. If you teach the exporter to emit @objc_direct methods in the compatibility header, why not just have it do so for all internal and/or private methods?

objc_direct implies visibility("hidden"), so a direct "treat this declaration as if it was objc_direct" doesn't seem useful given that the swift and obj-c code will generally not be in the same library.

It’s getting easier to split a class between Swift and ObjC, and it’s been possible for quite a while now to link Swift and ObjC together into the same dylib or executable.

1 Like

I think the goal is “expose a method defined in Swift to ObjC without the code size and execution time overhead of objc_msgSend”, which is no more unreasonable to want than the original __attribute__((objc_direct)) if you are trying to maintain ObjC source compatibility.

2 Likes

Sadly, @objc final is already a cromulent combination of features.

1 Like

Yes, the goal is to reduce code size, and to improve performance for exposing a Swift method to ObjC (i.e reducing overhead of @objc methods). A Swift method with @objc means it will be dynamically dispatched from ObjC side. The proposal is to introduce another attribute to say "expose it to ObjC but expose it as a direct ObjC method".

Trying to infer this based on information on Swift side within the compiler may not work, since we don't know the usage from ObjC side. As an optimization, we need to make sure ObjC side can compile and work correctly. For example ObjC side will enforce extra diagnostics for direct method, if the Swift compiler marks a certain Swift method as objc_direct, it may trigger warning/errors from ObjC side.

We also talked about the possibility of using @expose for Swift/ObjC interop, but that will enforce users to use Swift/C/C++ interop instead of thinking about Swift/ObjC interop.

1 Like

Having this functionality makes sense to me. As you noted, direct methods in ObjC aren't intended to be used for stable API and are always forced to be hidden in ObjC itself, so it's an interesting question how that interacts with Swift's visibility/access control model. We could say that an @objc_direct method always has to be internal when building with library evolution enabled, maybe.

2 Likes

Even requiring internal, it’d be useful for

I don’t know which use case y’all are prioritizing at the moment.

2 Likes

I still don’t understand how this differs from simply exposing Swift methods to Objective-C.

It's the same w.r.t exposing Swift methods to ObjC, the difference is @objc_direct exposes a Swift method as direct ObjC method, instead of a regular ObjC method, which changes the metadata gen and codegen in the compiler, for size saving and runtime perf improvement. To developers, there should be no difference, except they should not dynamically call such methods, e.g. respondsToSelector.

1 Like

OK, I think I finally see what I’ve been missing… if I understand correctly, you are proposing that marking a method @objc_direct would continue to emit the entry point that converts between the (Objective-)C calling convention and the native Swift calling convention.

I guess I still prefer teaching the ObjC compiler how to use the Swift calling convention, and then exposing the Swift methods directly. This has a few advantages: it needs no new keywords on the Swift side; it reduces codegen size even more than @objc_direct; and it avoids any questions about what it would mean to try to call an @objc_direct method across a resilient boundary.

Bridging does more than just calling conventions: it handles converting to/from String, Array, Data, or any other non-C-primitive bridged type; it wraps blocks as closures and vice versa; and it translates NSError ** to the special Swift error register (okay, that probably does count as calling convention). And you’d still want a way to control the selector that gets chosen. Swift methods and functions just aren’t close enough to C for this to work in the general case. (The still-unfinished @_cdecl has to do much the same.)

However, Clang does support functions and function pointers with the Swift calling convention, and that could be extended to objc_direct methods. I think it would be valid to say “the compiler should limit @objcDirect methods to only use types that are trivially bridged, since we’re trying to reduce overhead”. But then you’re not getting the easy win of writing a natural Swift API and calling it from ObjC with low overhead; you’re writing a dedicated bridging method.

1 Like

Hi @beccadax, we noticed your proposal Objective-C implementations in Swift and we are very excited to have the @implementation extension as a better path forward for exposing Swift to objC!

One question we have that's related to this post is, in the "Future directions" section, it mentioned

@implementation cannot create declarations that aren't supported by @objc. The most notable limitations include:

  • ...
  • __attribute__((objc_direct)) methods and @property (direct) properties.
  1. what will happen if we write @implementation extension in Swift while the @interface in ObjC header actually contains some direct method and property declaration? Will it throw diagnostics/errors, or will it simply still treat those direct method and property as if they were regular objC method and property (e.g. generate objC metadata for them)?
  2. Do you and the team have plans to support objc_direct in @implementation extension in the near future?
  3. Given you also mentioned that

@implementation heavily piggybacks on @objc 's code emission, so in most cases, the best approach to expanding @implementation 's support would be to extend @objc to support the feature and then make sure @implementation supports it too.

Would it be a good idea for us to actually implement the proposed @objcDirect as part of the @objc support, so that @implementation can then add /inherit such support too?

Thank you!

Looking at the current implementation, I think it'll treat direct members like ordinary ObjC members. (Which, if true, is a bug—it should reject these members because there's no way to correctly implement them without @objcDirect.)

I think that if both @implementation and @objcDirect get into the language, the combination of the two features should be supported. I figure whichever proposal gets approved last should be responsible for defining that @objcDirect works with @implementation.

In practice, I think "support" will mostly just mean that the ObjCImplementationChecker::matchesImpl() makes sure the direct-ness of the two members matches and that other parts of the checker diagnose mismatches appropriately—the code generation shouldn't need any changes. Once you have @objcDirect working in other situations, I'd he happy to help you get it working with @implementation too.