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!
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.
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.
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.
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.
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.
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.
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.