Swift 5.0 Class Internals
We look at how the implementation of the core of the object system for class implementation is the same in Swift as in Objective-C.
Join the DZone community and get the full member experience.
Join For FreeWe have seen a few things so far that are surprising with Swift. We also looked at some aspects of class implementations in Swift 4. Now that Swift 5 has been released, with a stable ABI, let's see how things compare to Objective-C, digging into internal data structures like we did in Objective-C.
Objective-C classes are defined elegantly, using C data structures that are relatively easy to follow and understand. A class definition is associated with a metaclass (to allow the class to be treated as a data object), as well as a group of functions treated as either class or member methods. Classes are associated with supported protocols and superclasses as well. We haven't specifically looked at protocol support nor at how methods are dynamically located, or how errors are handled. We'll look at all of these and start to filter conclusions through a security perspective.
The point of all this is to not only understand Objective-C internals, but to understand how Swift does similar things, so we can compare them and understand the real advantages and disadvantages of the two systems — we're not really looking at the languages, but rather how they're implemented internally so they can be executed.
Previously, the simple program we compiled was orders of magnitude larger than its Objective-C analog, for one thing, and it contains many, many more statically linked functions. This could very well be an artifact of how I compiled the program, but I used project defaults in Xcode with each example, so I don't feel that bad about it. What I have discovered though is the latest version of Xcode generates a MUCH smaller executable. In the interest of keeping things as easy as possible and preserving my reputation as lazy, we're going to use the smaller executable. I've looked at the Objective-C version compiled with the latest Xcode too, and it really hasn't changed, for what that's worth. But that's not too surprising really, Objective-C's been around a while. Swift really hasn't yet.
Here's our Swift program, so you don't need to re-reference:
import Foundation
class Printer {
var str_to_print: String
init() {
str_to_print = ""
}
func printMsg() {
print(str_to_print)
}
func printString(message: String) {
str_to_print = message
printMsg()
}
}
let printer = Printer()
printer.printString(message: "Hello World!")
I have to admit, I like the Objective-C syntax more. Could be because I (1) like C, and (2) don't like Java. Plus message passing syntax is cool.
Anyway, this yields this main()
entry point on compilation:
_main:
push rbp
mov rbp, rsp
push r13
sub rsp, 0x38
xor eax, eax
mov ecx, eax
mov dword [rbp+var_C], edi
mov rdi, rcx
mov qword [rbp+var_18], rsi
call _$s9swift_cmd7PrinterCMa
mov r13, rax
mov qword [rbp+var_20], rdx
call _$s9swift_cmd7PrinterCACycfC
mov r8d, 0xc
mov esi, r8d
mov qword [_$s9swift_cmd7printerAA7PrinterCvp], rax
mov rax, qword [_$s9swift_cmd7printerAA7PrinterCvp]
lea rdi, qword [aHelloWorld] ; "Hello World!"
mov edx, 0x1
mov qword [rbp+var_28], rax
call imp___stubs__$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC
mov rcx, qword [rbp+var_28]
mov rsi, qword [rcx]
mov rdi, rax
mov qword [rbp+var_30], rsi
mov rsi, rdx
mov r13, rcx
mov rax, qword [rbp+var_30]
mov qword [rbp+var_38], rdx
call qword [rax+0x80]
mov rdi, qword [rbp+var_38]
call imp___stubs__swift_bridgeObjectRelease
xor eax, eax
add rsp, 0x38
pop r13
pop rbp
ret
Okay, so message passing is gone, and the name decorating has changed. Here, we have straightforward functional semantics. What a shame. Anyway, let's try to track down how the class is defined. If we trace through the calls, we take a look at the first and see this disassembly (unfortunately, swift-demangle
isn't working on these Swift 5 mangled methods yet, so we need to trace through manually):
_$s9swift_cmd7PrinterCMa:
00000001000018d0 push rbp
00000001000018d1 mov rbp, rsp
00000001000018d4 sub rsp, 0x10
00000001000018d8 mov rax, qword [_$s9swift_cmd7PrinterCML]
00000001000018df cmp rax, 0x0
00000001000018e3 mov qword [rbp+var_8], rax
00000001000018e7 jne loc_100001903
----------------
00000001000018e9 lea rdi, qword [_$s9swift_cmd7PrinterCN]
00000001000018f0 call imp___stubs__swift_getInitializedObjCClass
00000001000018f5 mov rdi, rax
00000001000018f8 mov qword [_$s9swift_cmd7PrinterCML], rax
00000001000018ff mov qword [rbp+var_8], rdi
----------------
loc_100001903:
0000000100001903 mov rax, qword [rbp+var_8]
0000000100001907 xor ecx, ecx
0000000100001909 mov edx, ecx
000000010000190b add rsp, 0x10
000000010000190f pop rbp
0000000100001910 ret
I've included addresses in this disassembly as we do have a jump. But if we examine what _$s9swift_cmd7PrinterCN points to, we find this:
_$s9swift_cmd7PrinterCN:
struct __objc_class {
_$s9swift_cmd7PrinterCMm, // metaclass
_OBJC_CLASS_$__TtCs12_SwiftObject, // superclass
__objc_empty_cache, // cache
0x0, // vtable
__objc_class__TtC9swift_cmd7Printer_data+1 // data
}
Well, there we go! Some things from Objective-C are reused in Swift after all! And if we look at the metaclass, we see this:
_$s9swift_cmd7PrinterCMm:
struct __objc_class {
_OBJC_METACLASS_$__TtCs12_SwiftObject, // metaclass
_OBJC_METACLASS_$__TtCs12_SwiftObject, // superclass
__objc_empty_cache, // cache
0x0, // vtable
__objc_metaclass__TtC9swift_cmd7Printer_data // data
}
Exactly the same design that Objective-C uses, even using some of the same data structures (e.g. __objc_empty_cache
).
This means that the underlying class implementations between Objective-C and Swift are the same! Method call semantics may have changed (e.g. no more message passing), but the implementation of the core of the object system is the same in Swift as in Objective-C.
Opinions expressed by DZone contributors are their own.
Comments