Swift was designed to be a safe language and is has not fallen short of that promise. Here is how Apple describes the new language:
Swift is a new programming language for iOS, OS X, watchOS, and tvOS apps that builds on the best of C and Objective-C, without the constraints of C compatibility. Swift adopts safe programming patterns and adds modern features to make programming easier, more flexible, and more fun. Swift’s clean slate, backed by the mature and much-loved Cocoa and Cocoa Touch frameworks, is an opportunity to reimagine how software development works.
Remember that reflection in Swift is currently read-only and there’s no way to modify your program at runtime (with Objective-C-derived classes being an exception as you can stillclass_addMethod() in Swift). Perhaps this limitation will be incrementally lifted in subsequent language releases. However, Swift was designed to be safe – bringing true read-write reflection would break that dogma.
For the most part this safety is a good thing. This ensures that code executes as expected at all times by disallowing of other components modifying parts of your code. Objective C allowed making runtime changes via method swizzling and could be used for a variety of creative use cases like automated logging of certain methods.
Reflection in Other Languages
Between modern languages Swift is one of the very few that disallows readwrite reflection. Languages that allow modifying programs as they run include Java, C#, Go, Ruby, Python, Javascipt, PHP, Perl, Haskell, Lisp and R. In fact, from the 15 most popular languages* on GitHub the only ones not supporting readwrite reflection are C, C++ - and Swift.
So why do C and C++ not support reflection? For C++ it would be too much work to do add it and adding it would also be pretty complicated. For C the reasons are likely similar - on top of that making changes to mature and standardized languages take a long time.
For Swift none of the C++ and C arguments apply, so why the limitation on reflection? The answer is likely around security. Reflection is yet another attack vector in applications, or as Erwann Wernli-Schärer shares his thoughts on it:
The more reflective power you have the harder it is to ensure things are safe as they should. Reflection defeats notably static typing and can lead to run-time errors.
So if reflection is a security threat, how do some of the other major frameworks mitigate this? In the recent versions of C#, starting from .NET 4.5 Microsoft introduced much more strict rules - all code can read type information (similar to what Swift allows), but only trusted code with permission can modify code runtime. On top of this, code that is marked as security-critical cannot be accessed via reflection at all, offering a way to completely lock down sensitive code from inspection or modification.
Could the case be that Swift will support readwrite reflection only after putting some similar restrictions to .NET in place? This is yet to be seen, as the Swift team so far has not explained their decision behind omitting of this feature - or if we can expect it to be added at some later release.
Reflection and Mocking
Isolation is hard in real world as there are always dependencies (collaborators) across the system. That’s where concept of something generically called ‘Test Double’ comes into picture. A ‘Double’ allow us to break the original dependency, helping isolate the unit (or System Under Test (SUT) – as commonly referred)
- Dummy is simplest of all. It’s a placeholder required to pass the unit test.
- Fake is used to simplify a dependency so that unit test can pass easily.
- Stub is used to provide indirect inputs to the SUT coming from its collaborators / dependencies
- Mock Using behavior verification we can set expectations for SUT to exhibit the right behavior during its interactions with collaborators.
- Spy – Spy is a variation of behavior verification. Instead of setting up behavior expectations, Spy records calls made to the collaborator"
Dummies, fakes, stubs, mocks and spies are building blocks of unit testing for all modern languages. They can either be manually coded - which is more tedious - or created using mocking frameworks providing that provide easier to read APIs, removing a lot of the boilerplate code.
Mocking frameworks are all built on top of reflection, taking advantage of being able to change classes, types and objects runtime. They modify existing classes under the hood to either stub, mock or spy on functionality. They provide an API that is usually more pleasant to work with then when manually assembling objects.
The Language with No Mocking Framework
As Swift does not support readwrite reflection, it leaves the language without being able to create a framework to do the heavylifting for mocking. Mocking, stubbing and spying can still be done, but helper classes need to be manually created for every test, writing similar boilerplate code multiple times.
Is this really a bad thing? Many advocates of unit testing, including Uncle Bob often say that mocking frameworks are overused. So what advice does Uncle Bob give about not using mocking frameworks - something Swift developers will need to get used to:
I don't often use mocking tools. I find that if I restrict my mocking to architectural boundaries, I rarely need them.
Mocking tools are very powerful, and there are times when they can be quite useful. For example, they can override sealed or final interfaces. However, that power is seldom necessary; and comes at a significant cost.
Mocking across architectural boundaries is easy. Writing those mocks is trivial.
Writing your own mocks forces you to give those mocks names, and put them in special directories. You'll find this useful because you are very likely to reuse them from test to test.
Writing your own mocks means you have to design your mocking structure. And that's never a bad idea.
In Swift there is no other choice then to follow the advice from Uncle Bob: write your own mocks. Will developers coming from languages other then C and C++ to Swift miss having the tools at hand? Probably so. No one knows this better then the Erik Dörnenburg, creator of the most popular Objective C mocking library, OCMock. Answering the question on whether mocking is even need in Swift he says:
Mock frameworks have proven convenient in many languages. It's not that they are essential, but they do add convenience. Stubbing a factory method to return a mock is quick and easy. Creating a protocol and a wrapper, and using dependency injection is probably more sustainable, but it is also more work and looks more complex. As ever, having options and making the right choice seems key.
Like it or not, but in Swift the way forward is to follow Uncle Bob's advice and write your own mocks. It's a proven and sustainable approach - still, it would have been nice to have a choice.
See also the discussion on Readwrite reflection in Swift on the the Swift forms email list.
* Top 15 most popular programming languages on GithHub as of November 2015, ordered by priority (excluding markup and command line languages like CSS and Shell):
Featured Pragmatic Engineer Jobs
- Manager, Web Engineering at HashiCorp. $136-173K + equity. Remote (US).
- Manager, Web Engineering at HashiCorp. $123-180K + equity. Remote (US).
- Lead Frontend Engineer at Rooser. £100-120K London (UK).
- Software Engineer at Rooser. London (UK).
- Senior Frontend Developer at ePilot. €50-90K. Köln (Germany) or Remote (Germany).
- Senior Full Stack Engineer at ePiilot. €60-95K. Remote (Germany).
- Software Engineer at Air Space Intelligence. Boston, MA (US).
- Senior Full Stack Engineer - Developer Experience at Synthesia. €100K+. Remote (Europe).
- Senior Full Stack Engineer - Growth/Collaboration at Synthesia. €100K+. Remote (Europe).
- Senior Backend Developer (Python) at Octopus Kraken. Berlin or Munich (Germany).
- Senior Python Developer at Octopus Kraken. Paris (France).
- Senior Product Engineer, Frontend at Attio. £90-125K + equity. Remote (Europe).
- Staff Full Stack Engineer at POSH. $170-220K + equity. New York (US).
- Director, AI/ML at Sixfold AI. $195-225K + equity. New York or Remote (US).
- Founding Engineer (Full-Stack) at Strada. $140-190K + 0.5-1% equity. Remote (Global).
The above jobs score at least 9/12 on The Pragmatic Engineer Test. Browse more senior engineer and engineering leadership roles with great engineering cultures, or add your own on The Pragmatic Engineer Job board and apply to join The Pragmatic Engineer Talent Collective.
Want to get interesting opportunities from vetted tech companies? Sign up to The Pragmatic Engineer Talent Collective and get sent great opportunities - similar to the ones below without any obligation. You can be public or anonymous, and I’ll be curating the list of companies and people.
Are you hiring senior+ engineers or engineering managers? Apply to join The Pragmatic Engineer Talent Collective to contact world-class senior and above engineers and engineering managers/directors. Get vetted drops twice a month, from software engineers - full-stack, backend, mobile, frontend, data, ML - and managers currently working at Big Tech, high-growth startups, and places with strong engineering cultures. Apply here.