You can use Swift’s “private” access modifier to limit the reach of overrides and extensions, but not monkeypatches

by Joey deVilla on August 3, 2014

evil monkey pointing at swift code

What happens when you mark operator overload definitions as private?

swift kickWhile I was translating the code in the RayWenderlich.com article Sprite Kit Tutorial for Beginners from its original Objective-C to Swift, I got to the point where he provides some routines to do vector math on CGPoints. These are “standalone” utility functions and aren’t methods inside any class.

The first three functions are for vector arithmetic:

  • Adding two vectors together: (x1, y1) + (x2, y2) = (x1 + x2, y1 + y2)
  • Subtracting on vector from another: (x1, y1) – (x2, y2) = (x1 – x2, y1 – y2)
  • Multiplying a vector by a scalar: (x, y) * k = (k * x, k * y)

Here’s his original code, written in Objective-C:

I’ve always found methods like add(firstThing, secondThing) a little clunky-looking; I’d much rather write it as firstThing + secondThing. Since Swift supports operator overloading (Objective-C doesn’t), I decided to implemented the rwAdd(), rwSub(), and rwMult() functions as overloads of the +, -, and * operators. Here’s my equivalent Swift code:

Note that I added a private access modifier to each of the functions above. As I pointed out in my last Swift article, Swift’s access modifiers are based on files and modules, not the class hierarchy. In Swift, any entity marked private is visible and accessible within its own file, and invisible and inaccessible from outside its own file. I wanted to see what would happen to operator overloads.

Here’s what I found:

Operator overloads marked private are available within the file where they’re defined, and are not available outside that file. Outside the file where the private operator overloads were defined, any attempt to use them results in an error.

A little testing confirmed that I could add and subtract CGPoints and multiply them by scalars using the + operation from inside the same file, but couldn’t do so from inside in other files.

What happens when you mark extensions as private?

The next couple of functions that were in Sprite Kit Tutorial for Beginners were for getting and normalizing the length of a vector. Here’s the original Objective-C code:

Swift supports the addition of functionality to types by means of extensions. I thought that implementing these functions as computed properties of CGPoint in an extension would make for more elegant code — I’d rather write vector.length and vector.normalized than rwLength(vector) and rwNormalize(vector). Here’s what I wrote:

Note that I marked the entire extension as private. How would that affect their visibility inside and outside the file where the extension was defined?

The members of extensions marked private are available within the file where they’re defined, and are not available outside that file. Outside the file where the private extension members were defined, any attempt to use them results in an error, and auto-complete wouldn’t even list them.

Once again, testing confirmed that I could normalize and get the length of vectors represented by CGPoints from inside the same file, but couldn’t do so from inside in other files.

What happens when you mark “monkeypatches” as private?

Marking overrides and extensions as private led me to wonder what would happen if I marked a “monkeypatch” as private.

Monkeypatching is a term that’s used in the Python and Ruby developer communities, and it means dynamically replacing an existing class method with one of your own. It’s takes some effort to do in Objective-C (thanks to Joe Smith for the heads-up!), but it’s quite simple to do in Swift — and not just to methods, but properties as well. Here’s a quick example, in which I redefine the String class property utf16Count, which returns the number of UTF-16 characters in a string (it maps to NSString‘s length method):

With this extension, I’ve monkeypatched utf16Count so that it always returns 5, no matter what the number of UTF-16 characters in the string is.

Monkeypatching is powerful, and it’s sometimes useful, but as smarter people than I have pointed out, it can create more problems than it solves. As Jeff Atwood asked in Coding Horror, “Can you imagine debugging code where the String class had subtly different behaviors from the Stringyou’ve learned to use?”

That’s what got me thinking: what if private could be used to limit the scope of a monkeypatch, to limit the applicability of my warped verstion of utf16Count to a single file? Is such a thing possible? To answer these questions, I created this extension to String in one file:

This extension provides two things:

  • A monkeypatch for String‘s already-existent utf16Count property so that it always returns the value 5
  • A new property for String called someNumber, which always returns the value 10

In the same file where I defined my extension to String, I wrote this code:

And in another file, I defined the doOutsideCount method:

If making the extension private limited my redefinition of utf16Count to the file where I redefined it, the output of doOutsideCount() would be “UTF-16 count outside: 14”, since there are 14 UTF-16 characters in testString. I noticed that while typing in the code in this file, utf16Count was available to me in auto-complete.

Here’s the output that appeared on the console when I ran the app:

It appears that monkeypatching (redefining an existing member) in an extension marked private does not limit the monkeypatch to the file where the monkeypatch was defined. You can’t use private to limit the scope of a monkeypatch to a single file.

I changed the code in both files to try calling on someNumber, a method that didn’t already exist in String:

This wouldn’t even compile. Calling on the someNumber property of String outside of the file where I defined the private extension raises an error: 'String' does not have a member named 'SomeNumber'.

My conclusion from this little bit of experimenting is also this article’s title:

You can use Swift’s private access modifier to limit the reach of overrides and extensions, but not monkeypatches.

{ 2 comments… read them below or add one }

1 Joe Smith August 4, 2014 at 9:24 am

You can Monkey Patch in Objective-C using some of the exciting stuff declared in objc/runtime.h.

E.g., class_getInstanceMethod and class_exchangeImplementations. :)

2 Joe Groff September 8, 2015 at 5:46 pm

This isn’t really monkey-patching in the ObjC or Ruby sense. You’re declaring a new definition of ‘utf16count’, which shadows other definitions, but doesn’t change any behavior of other code at runtime. Swift extensions are designed not to impact code in other modules, so that they can be safely used without having to worry about clobbering extensions from other modules. The ‘private’ extension being visible across files is a bug which appears to be fixed in Swift 2.

Leave a Comment

Previous post:

Next post: