As an iOS developer — or any type of software developer — you’re eventually going to run into linker errors. Sometimes they’re easy to fix (i.e., you’re missing an #include for a header file). Sometimes they’re crazy complex, subtle, and very difficult to solve. Today we’ll talk about some tools (file, otool) and techniques (setting library target hardware architectures) you can use for solving difficult Xcode linker errors.
When writing robust iOS applications, you may want to, or be required to, link third-party libraries into your Xcode projects. You may be writing your own libraries. Why reinvent the wheel if someone else has written some solid code with an elegant and well-documented API? When carefully crafted, libraries written in say C++ or Java can be platform-independent — capable of being compiled to target different hardware architectures like Intel or ARM.
Let’s suppose you’re working on an iOS app for the iPhone/iPad. For the app to run on an iDevice, your app project’s Targets -> targetName -> Build Settings -> Architectures -> Valid Architectures need to be set to:
1 |
arm64 armv7 armv7s |
(Note that nowadays, “armv7s” is generally optional.) Say you have a static library (“archive”) like library.a. We could discuss dynamically linked libraries, but let’s keep it simple. You’ll add the library to your Xcode app project in Project -> Target -> Build Phases -> Link Binary With Libraries. You compile/link your project in the simulator or on an actual device by pressing the ⌘-C keys or by clicking Product -> Build and… say what!?!? You see the dreaded stop-sign-with-exclamation-point error icon () in the Toolbar. You look at Xcode’s Issue Navigator (compiler/linker warnings and errors) and find that you’ve got linker (ld) errors like:
- “ld: warning: ignoring file /path/to/library/library.a, missing required architecture i386 in file /path/to/library/library.a (2 slices)”
- “Undefined symbols for architecture i386: “_OBJC_CLASS_$_MethodName”, referenced from: objc-class-ref in libraryObjectCodeFile1.o”
1 2 3 4 5 6 7 8 |
Undefined symbols for architecture <strong>i386</strong>: "_OBJC_CLASS_$_ClassName", referenced from: objc-class-ref in libraryObjectCodeFile1.o objc-class-ref in libraryObjectCodeFile2.o ... "ClassName::MethodName", referenced from: ClassName::MethodName1() in libraryObjectCodeFile1.o ClassName::MethodName2() in libraryObjectCodeFile2.o |
The symbols — like class and method names — are “undefined” because i386 is speaking Intel-ese and arm64/armv7 is speaking ARM-ese. The binary file formats for the libraries are different. One is unintelligible to the other.
How do we solve this problem? Try to think this through before reading further… Hint: you need to build an app binary, the file which contains the machine code for an iDevice to execute. That binary file must be compatible with the iDevice. I’ll go make a cup of coffee with my new Keurig while you ponder this conundrum.
You set Valid Architectures to arm64 armv7 armv7s in your app project, but the linker errors are telling you that you’re “missing required architecture i386” and you have “Undefined symbols for architecture i386”. Sounds like you’re trying to link an app binary you meant for iPhone/iPad with an ARM processor with a library binary meant for a device with an Intel processor (or vice versa, it depends on the situation). Think of this metaphor: You’re trying to put a BMW transmission into a Toyota. It can’t be done.
If you have the source code for library.a, you can easily fix the problem. Remove the current reference to library.a in your app project in Project -> Target -> Build Phases -> Link Binary With Libraries. Open the library’s project in Xcode and change the Valid Architectures to arm64 armv7 armv7s. Do a Product -> Clean (keyboard shortcut ⌘-⇧-K), build, and drag and drop the newly-built library/archive into your app project. Clean your app project, rebuild, and your app will run on the iDevice!
Suppose you don’t have the source code for library.a. You’ll need to get a version of the library that was built for arm64 armv7 armv7s. Contact the vendor/creator of the library. What about looking at this problem more deeply, considering the fact that sometimes compilers and linkers don’t always give you wholly accurate information, i.e., yeah, there’s an error, but the message they’re spewing only points to the actual solution. Consider the fact that you may be dealing with a bunch of different libraries. What to do?
There are a couple of tools, “file” and “otool”, built into MacOS (Unix), that come in handy for determining the target hardware architecture of a library. Open up a Terminal window and change directory to the location where the library/archive lives. Run the “file” command and inspect its output:
1 2 3 4 5 |
$ file library.a library.a: Mach-O universal binary with 2 architectures library.a (for architecture x86_64): current ar archive random library library.a (for architecture i386): current ar archive random library |
The “file” tool is generally less verbose, but does have a lot of options you can try. If you want really verbose details, check out “otool:”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ otool -hv -arch all library.a Archive : library.a (architecture x86_64) library.a(libraryObjectCodeFile1.o) (architecture x86_64): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 4 1488 SUBSECTIONS_VIA_SYMBOLS library.a(libraryObjectCodeFile2.o) (architecture x86_64): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 4 1248 SUBSECTIONS_VIA_SYMBOLS library.a(libraryObjectCodeFile3.o) (architecture x86_64): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 4 1328 SUBSECTIONS_VIA_SYMBOLS library.a(libraryObjectCodeFile4.o) (architecture x86_64): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 4 1088 SUBSECTIONS_VIA_SYMBOLS library.a(libraryObjectCodeFile5.o) (architecture x86_64): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 4 848 SUBSECTIONS_VIA_SYMBOLS library.a(libraryObjectCodeFile6.o) (architecture x86_64): ... |
Suppose you were able to get a copy of library.a for the arm64 armv7 armv7s architectures. Let’s run the “file” command on the archive:
1 2 3 4 5 |
$ file library.a library.a: Mach-O universal binary with 2 architectures library.a (for architecture armv7): current ar archive random library library.a (for architecture arm64): current ar archive random library |
There you go! Put the compatible version of the library into your app’s Xcode project, clean, build, and you’ll be able to run the app on an iDevice!
As always, post a comment if you have questions or feedback.
Wow, a great tutorial, immediately solved my problem of attaching the third party archive static lib to my build phases tab. I couldn’t get this answer in stack overflow.
Vijay:
Glad I could help. Thanks for reading my blog!
– Andrew
The first couple of paragraphs casually mentioned the solution to a problem that no other forum or article on the internet seems to address: which file (lib.a) and where to put it (link with binaries).
So I can actually USE the library I’ve downloaded and built.
Thanks, I’ll keep reading your work for sure.