Swift and Objective-C Runtime Programming
A few months ago, a debate has been going on within the Objective-C/Swift developer community concerning the lack of dynamic features in Swift and the importance that runtime programming plays in Objective-C to build features that do not seem to be easily replicated in a pure Swift environment, at the time being.
Examples of that are Cocoa fundamental design patterns like the Responder Chain, NSUndoManager, KVC/KVO/Bindings, and Core Data, which strongly leverage Objective-C's dynamic features to grant easy coding and high decoupling.
More than about Swift being a dynamic language or not, concern is that Swift does not seem to provide anything comparable and Apple is not discussing what Swift features could be required/desirable to make possible to solve the same problems in Swift that were solved through dynamism in Objective-C.
InfoQ has spoken with Chris Eidhof, author of Functional Swift and Advanced Swift and creator of Deckset and Scenery, and Drew Crawford, Swift developer and author of the first statically-linked Swift/Linux program, to learn more about these potential issues.
InfoQ: Could you describe what is the importance of dynamic features in Objective-C and for Cocoa programming?
Chris Eidhof: Dynamic programming is a very overloaded term, even when we restrict it to Objective-C/Cocoa. For some, it means that a program shows dynamic behavior at runtime. For others, the use of KVO/KVC. Or late binding, subclassing, swizzling, runtime casting, dynamic typing, and so on. Depending on the person you talk to, it's probably an extended subset of one of these things.
In Cocoa, when people say "dynamic programming" it often means runtime programming. In Objective-C, using the runtime has been a very import foundation of most frameworks. By using runtime programming, we can write programs that are much shorter, and remove a lot of boilerplate. Little code and little boilerplate are good things. However, there are also dangers associated with it: when you rely on reflection and introspection features of a language, it can be hard to find bugs statically: you'll only find out at runtime.
Compared to Cocoa, UIKit has already moved away from runtime programming. Bindings don't exist, and KVO/KVC is much less important than it was in Cocoa. Swift moves way further in that direction.
Drew Crawford: Dynamic features are things that change the behavior of a program at runtime. Examples include things like duck-typing (casting unrelated types that implement similarly-named methods), creating new classes at runtime, method swizzling (changing functions at runtime), selectors (deciding what functions to call at runtime), etc.
Cocoa was designed around many of these features, and assumed their existence as a fundamental principle. Selectors are a key part of Cocoa's target-action pattern. Going between classes and strings is a key part of NSCoding. And over the years in hundreds of small places these dynamic patterns have embedded themselves deeply into the Cocoa fabric.
InfoQ: In what respect does Swift fall short on them? What is that Swift makes hard to accomplish that is more easily resolved in Objective-C?
Chris Eidhof: The reflection mechanism in Swift is still very immature, and some runtime features aren't available at all. For example, with Objective-C objects you can use key-value observing to observe properties of an object. In Swift, you can only use that with Objective-C object, but not with structs.
Many things seem harder in Swift if you're trying to solve them in a runtime-oriented way. The hardest part about giving up these runtime features is learning to think different. Almost all problems can be solved with much less code in Swift, but we need different approaches. Instead of using runtime programming, we can use features like closures, generics and protocols to solve the same problems in a shorter, safer way. For example, in the Swift Talk episode on Networking, we show how we can use generics to safely provide a very dynamic interface to a webservice.
Drew Crawford: One obvious example is XCTest, the Xcode testing framework. XCTest can discover new tests you write by searching at runtime for all functions that start with "test". Without that feature, you have to maintain a list of all your tests by hand so that XCTest knows how to call them. In practice, it's easy to forget to include a test in the list, and that test won't run, and so you aren't testing the things you think you are.
Method swizzling is frequently used (some would argue misused) to work around vendor bugs. It's pretty common to get stuck in some situation where a closed-source library has some problem, and by digging around in the runtime you can insert your own code to work around it. The catch is, it's likely to break when the vendor changes something, so it's a double-edged sword.
InfoQ: Do you envision a more dynamic Swift? Should some level of dynamism be added at the language or framework level possible ? Should Swift be dynamic at all?
Chris Eidhof: Dynamic dispatch and message passing is already possible, either by using classes or by using protocols. More runtime programming support can be very useful, but may come at the cost of safety and performance. Personally, I have not missed runtime programming at all, and I have been writing Swift full-time ever since the language got released publicly.
Instead of relying on runtime programming, I use all the other features that allow for dynamic behavior: closures, higher-order functions, protocols, generics, and so on. This way, I can have dynamic behavior that's statically checked by the compiler. In our book Advanced Swift we show all of these techniques in practice.
In a recent blog post, I wrote about how we can replace runtime programming (which is what makes Objective-C dynamic) with functions (which is what makes Swift dynamic). I reimplement the NSSortDescriptor API just by using functions.
Drew Crawford: There are a lot of dynamic features already. If you are on iOS/macOS, you have the full Objective-C runtime available, and you can bridge your Swift code into the runtime and go to town. So you can do dynamic dispatch, selectors, and method swizzling in Swift today.
The problem is mostly what we're going to do for platforms like Linux, which don't have the Objective-C tradition. And increasingly we're going to have programs that want to be portable across the major platforms, and so there's pressure on developers to stick to only those things that are portable when writing new code. That problem is largely unsolved.
But I think we have to consider why Swift exists in the first place. Swift is not Objective-C with modern syntax. That would be a far easier project to undertake (and has been undertaken at many points in the language's history). You do not need a new language, a new runtime, a new compiler, a new Foundation, if that is all you are trying to do.
Instead, Swift is a conclusion: that the way Objective-C is going is not where we want to go. That the next decade of incremental improvements at the next 10 WWDCs will not get us to the right language, and the only thing for it is to revisit some very fundamental ideas, rewrite a lot of code, and kill our darlings.
I don't mean to suggest the enemy here is dynamism, exactly. But I do think we need to let go of the idea that since Objective-C does things a certain way, Swift should also. If you like your Objective-C, you can keep it! Swift needs to chart its own course. It needs to be aware of the history, but it also needs to learn from it, and that will mean doing things its own way.
InfoQ: What is the challenge of making a more dynamic Swift? How could that be achieved without making Swift a less safe language?
Chris Eidhof: I don't think a whole lot of features are missing. Yet, there are two I'd like to see. First of all, adding more support for reflection could be very helpful, but it's can be a double-edged sword: it might also make it easier to write incorrect code.
Second, and more importantly, I am really looking forward to conditional conformance in protocols. This allows you to be way more expressive. For example, an Array does not currently conform to Equatable. The reason is: it can only be Equatable if all its elements are Equatable. At the moment, this is not expressible in the language, but adding conditional conformance is a very high priority of the core team.
Conditional conformance will also allow us to do datatype-generic programming, which is used in languages like Haskell. Datatype-generic programming is a typesafe way of writing programs that operate on the structure of your data (similar to runtime programming).
Drew Crawford: Two of the major problems plaguing Objective-C are performance and safety. What is less obvious is that these problems are an immediate consequence of Objective-C's dynamism.
In a highly dynamic language like Objective-C, the programmer has incredible power. You can open up core system libraries and place your own code inside of them. You can hook into the very fabric of method invocation itself, creating objects with an infinite number of methods, or create methods that wink in and out of existence over the lifecycle of an object. It is a staggering, dizzying level of power.
But with great power comes great responsibility. The more power you have, the less power is left for the compiler. And so the Objective-C compiler has blinders on. Your code *looks* like a simple for-loop through an array. But how do we know you have not replaced NSArray with some object that has an infinite number of methods? How do we know the array is not procedurally generated and has an infinite number of elements? This stuff sounds totally crazy, but in Objective-C it is street-legal, and inevitably somebody actually does it. So the compiler can barely get through a line of your program before resigning itself to exactly what you wrote, exactly the way you wrote it. And what you wrote is unlikely to be very fast or very safe.
In Swift, we rescind the programmer's license for omnipotent power. But what we get in the trade is a compiler with the blinders off. It can see much further into your code, and often has a bird's-eye view of a whole program. This lets it pull off optimizations that are truly breathtaking in scope, and it can sound alarms about subtle bugs that involve interactions between components at great distance. This is the force that attracts people to Swift, once you get beyond things like syntax and brace style. It's what gives Swift its character.
The puzzle is how we're going to support the things Objective-C developers want, while maintaining the speed and safety that Swift developers expect. The reality is we will never totally square the circle, but if we put our heads together we can get closer.
InfoQ: Are you aware of any work that has been/is being done in Swift development to make Swift more capable of addressing those concerns?
Chris Eidhof: Yes, almost all discussions happen on the mailing lists, and the swift-evolution repository on Github is a great resource. If that is too overwhelming, there is a weekly digest called Swift Weekly Brief.
Drew Crawford: One example is `_typeByName`, which arrives in Swift 3. It's the answer to Objective-C's `NSClassFromString`, allowing dynamic construction of classes.
The fact that the API starts with an underscore should fill you with the appropriate amount of fear that the design has not been entirely thought through and will likely change in the future.
InfoQ: Swift aims to address a broad scope, including server-side development, and system programming. How important is overall for Swift to address those concerns? Do you think the Swift dev team is listening?
Chris Eidhof: It's clear that the Swift team is listening very well. They are very active on all the Swift mailing lists, and go to great lengths to involve the community. Swift is a very ambitious language, and I think the team really found a sweet spot between ambition and pragmatism. However, listening does not mean adding every possible feature to the language. I'm very impressed how the Swift team always takes a step back, and tries to understand the actual problem, rather than implementing "one more feature".
Drew Crawford: Swift faces a lot of challenges right now beyond dynamism. The syntax and names of core functions are fairly unstable. There's no ABI compatibility. Generics aren't complete. These are large, burning buildings. The core team is very, very concerned about these problems – in my view, correctly.
When the proposals on dynamism come in, the response is never "no". The response is that it's far too important a problem to try to solve it while distracted by these other things. I empathize with the Objective-C developer who is annoyed by having to suffer for another year or two without better solutions on dynamism, but I think they will be more annoyed if we keep renaming half the standard library every year, and we have to pick our battles.
One thing to keep in view is that nobody in the world maintains more Objective-C code than Apple. Core Data alone probably has more dynamic tricks up its sleeve than the rest of the ecosystem combined. So the idea that Apple is unaware of the problem seems misguided; they are a lot better informed about both the benefits and costs of dynamism than perhaps anyone.
But I also think we need to let some things go. The Swift Way™ is to think first about static ways to approach problems that were solved dynamically in Objective-C, and then maybe introduce some dynamism when other approaches fail. If that means certain programs will always be better represented in Objective-C, we need to make peace with that, just as it's clear to many of us many programs are better represented in Swift. These differences give the languages character and flavor, and we need to give each of these languages permission to become their best selves, and to find their own path.
What Swift's path looks like is still quite open. I am confident it contains much more dynamism than the language has today. But that story remains to be written, and I think it is too early to make firm conclusions about how the book will end as the community is finishing the third chapter.
In this article, we addressed the concerns raised by several Objective-C programmers about the lack of dynamic features in Swift. Two developers deeply involved in the process of advancing the Swift language and its library ecosystem helped us understand the context of those issues and how Swift can eventually tackle them.
About the Interviewees
Drew Crawford is a software developer, writer, and consultant. He wrote the first statically-linked Swift/Linux program. When not working on Swift, he runs an Austin-based boutique development company, writing iOS applications and server software and licensing custom Swift technology to companies of all sizes. Author of the widely-read "Why Mobile Web Apps are Slow", his articles have been translated into a dozen languages and are assigned reading in mobile development classes at universities around the globe.
Chris Eidhof is a Swift developer from Berlin. He started objc.io, a number of conferences, is the author of Functional Swift and Advanced Swift, creator of Deckset and Scenery, and host of Swift Talk. In his copious spare time, he likes to run.