Why this happens
#import and linking are two separate steps. The import only tells the compiler that IOBluetoothDevice exists and what its methods look like. It does not put any code into your binary. At link time, ld has to find the actual compiled implementation of _OBJC_CLASS_$_IOBluetoothDevice, and it looks for it in the frameworks and libraries attached to your target — not in your header search paths.
So when you see:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_IOBluetoothDevice", referenced from:
objc-class-ref in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
the compiler was happy. main.o was produced and it references that class. The linker then searched every library in the link phase, found nothing that defines it, and stopped. The for architecture x86_64 part just tells you which slice of the build failed — the same error can appear for arm64.
A second, less common cause is an architecture mismatch: the library is linked, but the copy on disk was built only for 32-bit i386 or armv7, so there is no x86_64 code inside it to satisfy the reference.
How to fix it
1. Add the framework to the target’s link phase
This is the fix in the overwhelming majority of cases. IOBluetooth is a system framework on macOS, but system frameworks still have to be linked explicitly.
In current Xcode:
- Select the project in the navigator, then select your app or tool target (not the project itself).
- Open General → Frameworks, Libraries, and Embedded Content.
- Click +, search for
IOBluetooth.framework, and add it. Leave the embed setting at Do Not Embed — it’s a system framework. - Alternatively use Build Phases → Link Binary With Libraries → +. Both panels edit the same list.
In older Xcode the path is only Build Phases → Link Binary With Libraries → + → find IOBluetooth.framework → Add.
If you prefer flags over the UI, adding -framework IOBluetooth to Other Linker Flags (OTHER_LDFLAGS) does the same thing for a command-line tool.
After adding it, click the framework in the navigator and check the File Inspector on the right: the target membership checkbox for your target must be ticked. A framework that is in the project but not in the target is the classic reason the error survives the fix.
Your source stays as it is:
#import <Foundation/Foundation.h>
#import <IOBluetooth/objc/IOBluetoothDevice.h>
#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h>
int main(int argc, const char *argv[]) {
@autoreleasepool {
NSArray *devices = [IOBluetoothDevice pairedDevices];
for (IOBluetoothDevice *device in devices) {
NSLog(@"%i : %@", [device classOfDevice], [device name]);
}
}
return 0;
}
With modules enabled you can also write @import IOBluetooth; instead of the individual imports, but that still does not link anything by itself.
One thing worth knowing: IOBluetooth is macOS-only. If the target is an iOS app, the framework does not exist for that platform and no amount of linking will help.
2. Confirm the library has an x86_64 slice
If the framework is definitely in the link phase and the error persists, check what architectures the binary actually contains. For a static library:
lipo -info libSomeLibrary.a
You want to see x86_64 (and arm64 for Apple silicon) in the output. If it only lists i386 or armv7, that library predates 64-bit and cannot satisfy the reference. Rebuild it from source for a current architecture, or replace it with a maintained version.
For a framework bundle, point lipo at the binary inside it:
lipo -info SomeFramework.framework/SomeFramework
If you are stuck with a 32-bit-only dependency, the honest answer is that it is not usable in a modern build — Apple dropped 32-bit support, and forcing ARCHS = i386 is not a path forward.
3. Check that your own source files are compiled
When the missing symbol is one of yours rather than a framework class, the cause is usually a .m file that is not in Build Phases → Compile Sources. The header is visible, so the compiler accepts the call, but no object file defines the method. Select the file, open the File Inspector, and verify target membership; then add it to Compile Sources if it is missing.
4. Read the link command
The error message ends with use -v to see invocation for a reason. Adding -v to Other Linker Flags prints the full ld command, including every -framework and -l argument. If IOBluetooth is not in that list, the framework is not linked, no matter what the UI shows.
5. Clean before you re-test
After changing link settings, use Product → Clean Build Folder (⇧⌘K) and build again. Stale object files from a previous configuration can keep an old, incomplete link line alive.
How to prevent it
- Add the framework at the moment you write the first
#importfor it, not after the build breaks. - After adding any framework or library, check its target membership checkbox in the File Inspector.
- Run
lipo -infoon every third-party static library before adopting it. - Remember that a successful simulator build does not prove the device architecture links; build for both.
- When a symbol is missing from your own code, suspect Compile Sources before you suspect the linker.
FAQ
Why does the error mention x86_64 when my Mac is Apple silicon?
The architecture in the message is the one being built, not the one your Mac runs. Simulator builds on Apple silicon are arm64; x86_64 shows up when the target is set to build for an Intel destination or a universal binary that includes Intel. The fix is the same either way.
What is the difference between Undefined symbols and framework not found?
ld: framework not found Foo means the linker could not locate the framework at all — usually a missing search path or a framework that isn’t installed. Undefined symbols means the linker ran, searched the libraries it was given, and none of them defined the symbol. The first is a path problem, the second is a link-phase or architecture problem.
Do I need to embed IOBluetooth.framework in my app?
No. It is a system framework present on every macOS install, so it should be linked but not embedded. Embedding system frameworks bloats the bundle and can cause code signing problems during distribution.
Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.
