1. 12
  1. 12

    Some context for Apple tech newcomers:

    A pillar of Swift’s initial uptake was smooth interop with Objective-C, in which all of Apple’s application frameworks were written (most still are). “NSArray” and similarly-prefixed symbols are Objective-C classes from the Foundation framework — more or less ObjC’s standard library. In Objective-C, classes are reference types, and dispatch is always dynamic. Swift is performance-focused and has value type structs and enums, plus reference type classes. All of the above can have methods, and Swift will compile static, vtable, or dynamic dispatch depending on runtime possibilities.

    Usually, only classes can be compatible with Objective-C for interop. But as distinct from Objective-C’s NSArray class, Swift arrays are structs. The same is true for other basic types like other collections and the string type. So for the standard library types, there has always had to be a plan for how that bridging between statically-called value types and dynamically-called classes can happen without developer effort. That’s goal 2 in this article. The underscore-prefixed symbols used to accomplish that are the standard library implementation, not something an app developer uses.

    On Linux and Windows, where Objective-C was never part of the baseline, all the Objective-C interop logic is simply omitted by design. In the far future that may be true on Apple platforms as well. I believe Swift has C interop on any platform regardless of Objective-C.

    The ArrayTypes have full value semantics via copy-on-write (COW)

    “Value semantics” in the Swift world is as opposed to “reference semantics”. It just means “does it act like a value type or act like a reference type,” under situations like passing it to a function or saving it to a new variable. A class object is a reference, so you expect it to copy the reference and share the object. A struct is a value, so you expect it to make a unique copy, or at least behave like it did. But to have both growable arrays and known allocation sizes to hold them, a collection must back its storage with a reference. When a struct contains a reference, by default that will just get copied, which would share array storage and break value semantics. In order to let programmers think about arrays like they are values, the data should be copied. And to preserve performance under that constraint, it will only Copy On Write: When an Array gets a call that would mutate, it checks whether its backing store has only one owner. If not, it makes its own copy first. Then the mutation proceeds. Making a COW type is something a Swift programmer might have to do once in a blue moon, but usually the standard library has what you need.

    This is a pretty good example of the compiler and standard library doing an awful lot for you, while not abstracting it so far away that you never learn it. A design principle of Swift is that you don’t have to keep Objective-C bridging or COW types or ARC memory management top of mind every day, but that you should understand them and have to think about them sometimes.

    1. 5

      Arrays being COW sounds like awfully dangerous performance footgun. Though considering that the language is largely used for macOS/iOS apps and nothing critical, it’s probably fine.

      1. 4

        It doesn’t cater to anything system-level yet, that’s right. The current intended use case is application-level stuff, they’re working on server stuff, and system level is an eventual goal. There are unimplemented plans for opt-in Rust-like ownership, for instance.

        As for footgun severity, well, it does hide something from you in the sense that copies happen on some but not all mutations. But this copy is shallow, not deep, and the idiomatic norm is for everything to be a constant and not a variable, which prevents mutations and therefore COW operations. At the application level the iOS / Mac dev community has been pleased with the performance, and we’ll see how it goes as the language spreads to more performance-critical use cases.

        1. 3

          At the application level the iOS / Mac dev community has been pleased with the performance

          Only insofar as they haven’t actually measured it.

          For example, I have seen several GitHub repos stating “we use Swift Codable, so it is fast”.

          Swift Codable is horrendously slow at around 10MB/s for encoding/decoding JSON on a fairly top-of-the-line 13” MBP/Intel. To compare, the actual SSD does 2 GB/s, and so is 200 times faster.

          I had an article series showing how to get around 20-30x faster speed using Objective-C.

          1. 3

            Your work looks promising for an app that heavily consumes JSON! I imagine the reason some of us haven’t focused on that problem is because we’re calling simple APIs, the objects are not huge, and decoding doesn’t show itself to be an outstanding bottleneck compared to the network call.

            In the first few years of Swift, everyone wanted to use its richer types in our model layers, and so our JSON-serialized domain models had to come along for the ride. Without Objective-C’s dynamism that was a tall order. There were a ton of libraries about it; none of them amazing. Like you wrote in your series, Codable was faster than those libraries, so it was fast compared to what early Swift devs had grown used to, but not compared to NSJSONSerialization producing plain untyped dictionaries. Really it was more of a standardization win than anything else.

            1. 1

              So we’ve come from “Swift is performance focused” first to “well, our users are pleased with the performance” (as long as they don’t measure) and finally to, essentially “well, performance isn’t really that important”.


              1. 3

                My post was about context for people who are interested in how Swift arrays work. I’m not clear on why you’re picking a fight, but I’m not interested. Good luck with the book.

      2. 4

        Swift is performance-focused and has value type structs and enums

        While Swift may be “performance focused” its performance is generally quite a bit worse than Objective-C. Sometimes amazingly worse, very rarely comparable or slightly better and usually noticeably worse.

        And also very significantly less predictable, which is arguably even worse for real-world performance.

        And of course Objective-C also has value type structs (and reference type structs) and enums, though admittedly the enums are nothing like Swift enums.

        And you can use C arrays for those value types, and even have those allocated on the stack (making sure they don’t blow it) or inline. While there may be a way to create a Swift array where the underlying storage is guaranteed to not be heap allocated, I haven’t figure it out yet.

        1. 3

          Safety is probably Swift’s first focus before performance. You can’t just drop down to C like ObjC could, and although you can write unsafe code you have to jump through significant hoops. The level of dynamism in Objective-C while still being so performant, even without any C escape hatches, was really something special. One thing I really miss is the speed of the compiler, though the safety and richer types feel worth it to me.

          Regarding the two languages’ structs and enums, yeah, they’re not the same beasts at all. Objective-C has what C has, while Swift uses the same keywords for two kinds of value-type objects that can have methods, conform to protocols, and participate in polymorphic dispatch. What I was trying to establish with that quoted line is the reason Swift uses multiple kinds of function dispatch depending on the situation.

          1. 3

            Yeah I’ve run into a lot surprising performance pitfalls with Swift. For performance sensitive code, I just switch to Objective-C now. It is as you said much more predictable, and IMO much easier to optimize being simpler than swift and having the option to easily fallback to C or C++ when necessary. That being said, for general purpose code I find the Swift’s robust type system a pleasure to use. I’ll take the performance tradeoff. Optionals and enums have been such a huge boost to the reliability and comprehensibility of my apps.

            1. 2

              For performance sensitive code, I just switch to Objective-C now

              Yeah, that’s pretty much what I recommend in my performance book.