This document describes how Swift interoperates with Objective-C code and the Objective-C runtime.
Interactions between the Swift runtime and the Objective-C runtime are implementation details that are subject to change! Don't write code outside the Swift runtime that relies on these details.
This document only applies on platforms where Objective-C interop is available. On other platforms, Swift omits everything related to Objective-C.
Swift generates calls to objc_msgSend
and variants to send an Objective-C
message, just like an Objective-C compiler does. Swift methods marked or
inferred as @objc
are exposed as entries in the Objective-C method list for
the class.
All Swift classes are also Objective-C classes. When Swift classes inherit from
an Objective-C class, they inherit in exactly the same way an Objective-C class
would, by generating an Objective-C class structure whose superclass
field
points to the Objective-C superclass. Pure Swift classes with no superclass
generate an Objective-C class that subclasses the internal SwiftObject
class
in the runtime. SwiftObject
implements a minimal interface allowing these
objects to be passed around through Objective-C code with correct memory
management and basic functionality.
Swift classes can be generated as part of the binary's static data, like a normal Objective-C class would be. The compiler lays down a structure matching the Objective-C class structure, followed by additional Swift-specific fields.
Some Swift classes (for example, generic classes) must be generated at runtime.
For these classes, the Swift runtime allocates space using MetadataAllocator
,
fills it out with the appropriate class structures, and then registers the new
class with the Objective-C runtime by using the SPI objc_readClassPair
.
Note: stub classes are only supported on macOS 10.15+, iOS/tvOS 13+, and watchOS 6+. The Objective-C runtime on older OSes does not have the necessary calls to support them. The Swift compiler will only emit stub classes when targeting an OS version that supports them.
Stub classes can be generated for dynamically-generated classes that are known at compile time but whose size can't be known until runtime. This unknown size means that they can't be laid down statically by the compiler, since the compiler wouldn't know how much space to reserve. Instead, the compiler generates a stub class. A stub class consists of:
uintptr_t dummy;
uintptr_t one;
SwiftMetadataInitializer initializer;
The dummy
field exists to placate the linker. The symbol for the stub class
points at the one
field, and uses the .alt_entry
directive to indicate that
it is associated with the dummy
field.
The one
field exists where a normal class's isa
field would be. The
Objective-C runtime uses this field to distinguish between stub classes and
normal classes. Values between 1 and 15, inclusive, indicate a stub class.
Values other than 1 are currently reserved.
An Objective-C compiler can refer to these classes from Objective-C code. A
reference to a stub class must go through a classref
, which is a pointer to a
class pointer. The low bit of the class pointer in the classref
indicates
whether it needs to be initialized. When the low bit is set, the Objective-C
runtime treats it as a pointer to a stub class and uses the initializer function
to retrieve the real class. When the low bit is clear, the Objective-C runtime
treats it as a pointer to a real class and does nothing.
The compiler must then access the class by generating a call to
the Objective-C runtime function objc_loadClassref
which returns the
initialized and relocated class. For example, this code:
[SwiftStubClass class]
Generates something like this code:
static Class *SwiftStubClassRef =
(uintptr_t *)&_OBJC_CLASS_$_SwiftStubClassRef + 1;
Class SwiftStubClass = objc_loadClassref(&SwiftStubClassRef);
objc_msgSend(SwiftStubClass, @selector(class));
The initializer function is responsible for setting up the new class and returning it to the Objective-C runtime. It must be idempotent: the Objective-C runtime takes no precautions to avoid calling the initializer multiple times in a multithreaded environment, and expects the initializer function itself to take care of any such needs.
The initializer function must register the newly created class by calling the
_objc_realizeClassFromSwift
SPI in the Objective-C runtime. It must pass the
new class and the stub class. The Objective-C runtime uses this information to
set up a mapping from the stub class to the real class. This mapping allows
Objective-C categories on stub classes to work: when a stub class is realized
from Swift, any categories associated with the stub class are added to the
corresponding real class.
This document is incomplete. It should be expanded to include:
- Information about the ABI of ObjC and Swift class structures.
- The is-Swift bit.
- Legacy versus stable ABI and is-Swift bit rewriting.
- Objective-C runtime hooks used by the Swift runtime.
- And more?