Monday, 8 December 2014

Understanding XCode, iOS, iPhone and Simulator Architectures

Architectures seem to cause so many problems in iOS/XCode development and its not suprising. Searching for problems I have had have uncovered so many problems, all of them seem to have different solutions although they are all really related to the same thing.

The types of problems you encounter using consist of Linker errors from shared libraries (ignoring file X missing required architecture Y in file Z) or not getting any output from your program or even finding it doesn't run or cannot be installed on certain devices.

The problems stem from:
  1. XCode sucks as an IDE
  2. There are several architectures flying around
  3. XCode does something weird with Active Architecture Only
  4. The fact that there are "valid" architectures as well as architectures is confusing
  5. There are lots of different target devices as well as the simulator, which doesn't actually simulate a device but runs on the host's architecture!
Fortunately, when the pieces are placed in order, it is not too difficult to understand, although you might find yourself hunting through un-necessary hassle to find the correct settings!

Warning: You might find the errors appear sometimes and not others, when linking. I think this is related to way the project is marked as invalid and "build required". What this means is that sometimes you won't have broken or fixed something, it'll just pretend to be OK until the next time it does a full build and fails again!
You should probably keep backups of your projects in case you royally screw something up and have to go back again. This is good practice anyway using git or subversion, for instance.

Firstly, you presumably know that architectures relate to the hardware of the various iPhone and iPad devices. The reason this is important is that at the lowest level, the way numbers are represented in memory and/or the instruction set for programs is simply different on different hardware. The way the program is compiled needs to be different. Another variable is that some older devices (pre iPhone 5s) were only 32 bit, whereas all the newer models are 64 bit (although they still support 32 bit apps).

So, we start from there. We will look at probably the most complicated example: a shared library that needs to support a load of architectures including the simulator. If you open your library project and go into the project settings, under Build Settings, you will see Valid Architectures and Architectures. The output will include any architectures that appear in both lists. Why have both? I'm not sure but it is likely to do with code warnings - you statically set the full set of architectures you will use in Valid Architectures and then you might change the Architecture as you are testing.

Setup your Library Architectures

In my library, I use the shortcut $(ARCHS_STANDARD) for both architecture settings, which in XCode 6.1.1 includes armv7 and arm64, which should work on most newer devices. The problem I now have (is it still a problem?) is that my library will also be run under the iOS simulator, which itself is likely to run on either i386 or x86_64 on newer Macs. It also needs to specify that it is a simulator build and all of this cannot be accomplished in XCode directly. To avoid building both separately, you can use another target, mine's called UniversalLib, and this runs a script that builds the normal build that would happen directly but then also runs another build into a separate directory and which specifies the simulator sdk and the i386 architecture. The script then uses lipo to glue these together into a single "fat" library. I can't find the original link I used but there are plenty of guides like this one. Once this is done, you can run "lipo -info libFile.a" from a terminal and it will list the architectures contained in the universal library. In my case, this is i386, armv7 and arm64.

Link the Library to your App

So, we now need to link to this library in the usual way, making sure we are linking to universal one, not one of the other two that were used to create it.

Build Your App

This is the part that seems easiest to cause confusion. If you just build what you have, it might or might not succeed. This is because XCode sucks with its configuration management. You have devices, targets, schemes and architectures all over the place and it is not clear how all of these things add together.

If you are not targetting older iPhones, you should probably have your app valid architectures and architectures set to $(ARCHS_STANDARD) to give you armv7 and arm64. This is a safe starting point. There is another setting "Build Active Architecture Only" which can be very confusing because what it does is not made very clear to the developer! It is usually set to "Yes" in debug and would generally always be set to "No" for release (archive) builds. "No" is easiest, it means build all of the architectures in "architectures" that appear in "valid architectures". This is important for the binaries you distribute because that is how it works on multiple devices. When you are debugging, however, there is no point building everything when you will only be testing on a single device, it is just going to slow you down. You set "Build Active Architecture Only" to Yes and it only builds one. Which one? That is slightly harder to work out but if your current target is set to "iOS Device" i.e. the simulator, the active architecture will be your host architecture, in my case x86_64 (I think it also allows i386). If you have a real device plugged in and that is selected, then that will be the active architecture. If you try and build at this point (I had iPhone 6 selected but not plugged in) then XCode will look for the host architecture in the linked libraries (x86_64 in my case) and fail linking if it isn't there.

XCode Schemes

What can also make this more complicated is the XCode schemes. If you go into Product->Schemes->Manage Schemes, you will find for EACH device, a set of schemes for Build, Run, Test, Profile, Analyse and Archive. In each of these, you set whether the build is for debug or release (and therefore whether it picks up the debug or release build settings). Archive is how you distribute applications and would almost always be Release but the other settings depend on what you are doing and whether you need to debug.

Build for the Correct Scheme

Once you set these up, you then need to make sure you are building for the correct scheme and device. For some reason, this is not really obvious like it is in Visual Studio. You can choose the device easily enough but what scheme does Build build? I don't know but you can choose Product->Build for->Running or whatever, to make sure it is correct.

Once you have looked in all of these places, you should be able to more easily follow the ridiculously complicated flow from library to app and work out why it errors!

What I haven't talked about is what happens if the library is from someone else and doesn't seem to have the correct architectures. You can obviously request the correct architectures but if that is not possible and they only provide, for instance, i386, you might have to downgrade your own architectures to 32 bit if you want to use that library. Google it!

Good luck, you'll need it.
Post a Comment