What happens when you mark operator overload definitions as private
?
While 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 CGPoint
s. 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:
// Objective-C static inline CGPoint rwAdd(CGPoint a, CGPoint b) { return CGPointMake(a.x + b.x, a.y + b.y); } static inline CGPoint rwSub(CGPoint a, CGPoint b) { return CGPointMake(a.x - b.x, a.y - b.y); } static inline CGPoint rwMult(CGPoint a, float b) { return CGPointMake(a.x * b, a.y * b); }
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:
// Swift // Vector addition private func + (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x + right.x, y: left.y + right.y) } // Vector subtraction private func - (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x - right.x, y: left.y - right.y) } // Vector * scalar private func * (point: CGPoint, factor: CGFloat) -> CGPoint { return CGPoint(x: point.x * factor, y:point.y * factor) }
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 CGPoint
s 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:
// Objective-C static inline float rwLength(CGPoint a) { return sqrtf(a.x * a.x + a.y * a.y); } // Makes a vector have a length of 1 static inline CGPoint rwNormalize(CGPoint a) { float length = rwLength(a); return CGPointMake(a.x / length, a.y / length); }
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:
// Swift private extension CGPoint { // Get the length (a.k.a. magnitude) of the vector var length: CGFloat { return sqrt(self.x * self.x + self.y * self.y) } // Normalize the vector (preserve its direction, but change its magnitude to 1) var normalized: CGPoint { return CGPoint(x: self.x / self.length, y: self.y / self.length) } }
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 CGPoint
s 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):
extension String { var utf16Count: Int { return 5 } }
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 String
you’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:
// File: ViewController.swift private extension String { var utf16Count: Int { return 5 } // already defined in String var someNumber: Int { return 10 } // a new addition to String }
This extension provides two things:
- A monkeypatch for
String
‘s already-existentutf16Count
property so that it always returns the value5
- A new property for
String
calledsomeNumber
, which always returns the value10
In the same file where I defined my extension to String
, I wrote this code:
// File: ViewController.swift let testString = "This is a test" println("UTF-16 count inside: \(testString.utf16Count)") doOutsideCount()
And in another file, I defined the doOutsideCount
method:
// File: SomeOtherFile.swift func doOutsideCount() { let testString = "This is a test" println("UTF-16 count outside: \(testString.utf16Count)") }
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:
UTF-16 count inside: 5 UTF-16 count outside: 5
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:
// File: ViewController.swift func doOutsideCount() { let testString = "This is a test" println("someNumber inside: \(testString.someNumber)") } // File: SomeOtherFile.swift func doOutsideCount() { let testString = "This is a test" println("someNumber inside: \(testString.someNumber)") }
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 replies on “You can use Swift’s “private” access modifier to limit the reach of overrides and extensions, but not monkeypatches”
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. :)
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.