bevkjbvkbdkdxoeoziejdoiehz fiugebfuyegwik

Swift access modifiers

This Twitter exchange encouraged me to write a few words on what effects the Swift access modifiers have.

Let's consider this example and that we want to use it in a target/action context:

import Foundation

class Thing : NSObject {
  func tap() {

Using an internal function or even a public one will simply just work, but if we change the visibility to private it will no longer work. The first naive theory would be that this is tied to symbol mangling being different for private methods.

Little sidebar on what symbol mangling is: it is the mechanism which takes a Swift construct and transforms it to a string that is valid as a symbol in the resulting binary and also unique. If you want some more information on this, read Mike Ash's excellent article on the topic.

So, let's check out the difference in symbol mangling between different access modifiers. First the internal one:

$ xcrun swiftc test.swift 
$ nm test|grep tap
0000000100001050 t __TFC4test5Thing3tapfS0_FT_T_
$ xcrun swift-demangle __TFC4test5Thing3tapfS0_FT_T_
_TFC4test5Thing3tapfS0_FT_T_ ---> test.Thing.tap (test.Thing)() -> ()

Now the private variant:

$ nm test|grep tap
00000001000010a0 t  __TFC4test5ThingP33_83378C430F65473055F1BD53F3ADCDB73tapfS0_FT_T_

OK, this must be it - the selector mechanism knows nothing about that extra UUID-looking stuff in the private symbol. Turns out that adding the @objc attribute (or @IBAction for that matter) will make the private function work as an action, though, while not affecting the way symbols are mangled, so this cannot be the reason for the difference in behaviour. It's still interesting to know and actually makes it much harder to somehow manually call a private Swift function even if we know that it exists, because only its own compilation unit knows about the exact value of that extra UUID.

Next route of investigation is using the trusty old Objective-C runtime APIs, considering that target/action is an ObjC mechanism. With the code from this snippet, we can list all methods on our class which are known to the runtime:

import ObjectiveC.runtime

var methodCount : UInt32 = 0
var methods : UnsafeMutablePointer<Method> = class_copyMethodList(self.dynamicType, &methodCount)

for i in 0..<methodCount {
    let name = NSStringFromSelector(method_getName(methods[Int(i)]))

The result is that private methods do not show up there at all, so they are invisible to the ObjC runtime. This is nice, because it eliminates another way to bypass the access modifiers at runtime. I guess this excessive hiding is also the reason for private constructs not being testable at the moment.

To conclude, common Objective-C mechanisms will not work on private functions, because they are hidden from the runtime. In case you want to limit the privateness to Swift only, you can utilize the @objc, @IBAction or @dynamic attributes.

UPDATE: Turns out the reason why public/internal methods are not hidden from the ObjC runtime is that @objc gets implicitly added for those in Objective-C compatible Swift classes.

The compiler does not automatically insert the @objc attribute for declarations marked with the private access-level modifier.