NEW: Learn SwiftUI with my free YouTube video series! >>

How to fix “argument of #selector refers to instance method that is not exposed to Objective-C”

Swift version: 5.0

Paul Hudson    @twostraws   

Swift 4 changed the way Swift interacts with Objective-C in a way that will impact the code of most developers. Fortunately, there are a couple of fixes available, neither of which take too long to implement.

First, let’s take a look at what’s changed and why. In Swift 3 all methods from a class were automatically compiled so that they were available both to Swift code and to Objective-C code, which maximized compatibility.

However, that had a cost. Here’s what Doug Gregor wrote when proposing the change for Swift 4:

There is a cost for each Objective-C entry point, because the Swift compiler must create a "thunk" method that maps from the Objective-C calling convention to the Swift calling convention and is recorded within Objective-C metadata. This increases the size of the binary (preliminary tests on some Cocoa[Touch] apps found that 6-8% of binary size was in these thunks alone, some of which are undoubtedly unused), and can have some impact on load time (the dynamic linker has to sort through the Objective-C metadata for these thunks).

So, all these thunk methods had to be generated whether or not they were used, which wasn’t ideal. As of Swift 4, this has been dramatically scaled down so that these thunks are generated only when absolutely required, which means any time you write Swift code that needs to be called from Objective-C you will need to tell Swift what to do.

If you don’t think this happens often, here are some examples:

navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addSong))

let tap = UITapGestureRecognizer(target: self, action: #selector(userDoubleTapped))

let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(chooseNewSong), userInfo: nil, repeats: true)

performSelector(inBackground: #selector(checkWikipedia), with: nil)

NotificationCenter.default.addObserver(self, selector: #selector(userLeavingApp), name: UIApplication.willResignActiveNotification, object: nil)

let lookup = UIMenuItem(title: "Applause", action: #selector(applaudGreatMusic))

undoManager?.registerUndo(withTarget: self, selector: #selector(undoPlaying), object: nil)

All seven of those examples ask some Objective-C code (e.g. NotificationCenter or Timer) to call our Swift code, which means all seven of those will stop working in Swift 4 unless you take action.

The error you’ll see looks like this: “Argument of '#selector' refers to instance method 'addSong()' that is not exposed to Objective-C,” and your code will refuse to build until you choose a solution. The key there is the #selector part: that’s the giveaway that you’ll need to use @objc with whatever method is being called.

So, that’s the problem and why it even exists. Let’s turn to the solutions, and there are two to choose from.

First, you can mark individual methods using the @objc attribute, like this:

class Printer {
    @objc func print() {
        // code

That instructs the Swift compiler to make an Objective-C thunk for that one method. This means you retain nearly all the performance benefits of the new Swift 4 approach – the thunk is generated only when needed.

The second option is to use the @objcMembers attribute on your whole class or struct, like this:

@objcMembers class ViewController: UIViewController {
    // code

That tells Swift to automatically generate Objective-C thunks for all methods in the class, so you don’t need to mark them individually using @objc.

Now, there are two important times when @objc isn’t needed:

  1. When you’re using @IBAction to connect an event from a storyboard. The @IBAction attribute automatically implies @objc, so you don’t need both.
  2. When you’re implementing a method from an Objective-C protocol, that automatically implies @objc because it doesn’t make sense otherwise.

Remember, if @objc is required but not present, Xcode will refuse to build your code – it’s not the kind of thing you can just forget.

Honestly, I think it’s sad that one of the world’s most progressive languages is having to look backwards like this, but it looks like we’re stuck with this change.

LEARN SWIFTUI FOR FREE I wrote a massive, free SwiftUI tutorial collection, and also have a growing list of free SwiftUI tutorials on YouTube – get started today!

Available from iOS 2.0

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.7/5