Franklin Webber

General cutup and roustabout

Universal Frameworks for iOS Made Easy!

On a recent iOS project we wanted to integrate the Facebook iOS SDK. The integration went smoothy. We copied in the compiled library and the required headers and were able to focus on the login, authentication flow.

Problem

However, we later ran into trouble with the library working on the device but not in the simulator. We would fix it for the tests and the simulator but find out we had broken it again for the device. This back and forth continued until we had found out about Universal Frameworks.

Narrative

We needed to compile the library in a format that would work on both on the iPhone and the simulator. Google lead us to this invaluable post that outlined the arduous path of creating a universal framework. A journey indeed filled with peril as there are so many opportunities along the way to take a misstep and lose your way.

To the author’s credit, he does a good job of outlining the necessary steps to succeed. It is the shear overwhelming number of Xcode’s build settings that can get in the way with crossing the finish line.

As the project continued we came again to a similar crossroads as we needed to include Twitter authentication. We chose to use the MPOAuth library but again found the need to generate a universal framework to save us from the same particular annoyance (and others) that arose when it came time to integrate the library. We followed through the same treacherous steps.

Having to do this twice has taught me that I do not want to do it again. There is simply too many steps to get right. It was something that needed to be easier and involve a lot less mouse-driven-development.

Solution

Xcoder had the majority of the functionality to get started. However, it did not have the ability to create some core components like: Aggregate Targets, Copy Headers Build Phases, Run Script Build Phases. At least it did not until this weekend.

I have created a gist that will generate a universal framework with sources and header files. This should successfully boil the the multiple page instructions into roughly 100 lines of ruby code.

Perhaps the most valuable part is the ability to set the multitude of required build settings, previously requiring deft configuration hunter skills, simply with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
target.create_configurations :release do |config|
  config.always_search_user_paths = false
  config.architectures = [ "$(ARCHS_STANDARD_32_BIT)", 'armv6' ]
  config.copy_phase_strip = true
  config.dead_code_stripping = false
  config.debug_information_format = "dwarf-with-dsym"
  config.c_language_standard = 'gnu99'
  config.enable_objc_exceptions = true
  config.generate_debugging_symbols = false
  config.precompile_prefix_headers = false
  config.gcc_version = 'com.apple.compilers.llvm.clang.1_0'
  config.warn_64_to_32_bit_conversion = true
  config.warn_about_missing_prototypes = true
  config.install_path = "$(LOCAL_LIBRARY_DIR)/Bundles"
  config.link_with_standard_libraries = false
  config.mach_o_type = 'mh_object'
  config.macosx_deployment_target = '10.7'
  config.product_name = '$(TARGET_NAME)'
  config.sdkroot = 'iphoneos'
  config.valid_architectures = '$(ARCHS_STANDARD_32_BIT)'
  config.wrapper_extension = 'framework'
  config.info_plist_location = ""
  config.prefix_header = ""
  config.save!
end

Building iOS Apps in the Shell

The current state of iOS command-line tools for iOS is abysmal especially if you are familiar and comfortable with a particular feedback loop for building, testing, and deploying. It is code savagery! Plain and simple!

Problem

I wanted the ability to easily build Xcode targets from bash.

Narrative

As an iOS developer, if you use any testing library requiring an additional, independent target you find yourself within Xcode re-selecting targets and platforms during development to validate functionality. Xcode’s programming is predominately Mouse-Driven Development (MDD) and great for initial discovery but that begins to break down as you start to identify multi-target operations you want to perform quickly. This operation of switching targets/platforms is a small cost if you write a lot of test or application code up front. This fails when you want to test drive your development as this small operational cost adds up as you begin to refine your feedback loop.

A project Substantial inherited came with a rudimentary Rakefile with a number of tasks and helper methods defined to assist with building and testing an iOS application from the command-line. It was awesome! As long as I did not block the IOS simulator I could write code and tests and get more immediate feedback.

Sadly, it was largely wrong: composed of a number of variables and hard-coded values that were based on assumptions of how the project was built. We hit our first major problem upgrading all the developers from Xcode 3.x to Xcode 4.x. All of our command-line tools and files broke and that quick feedback loop we became accustomed to broke down. Because of it our team and others delivered functionality that broke several tests that we later, out of context, struggled to resolve.

With the start of our next project I immediately ported over the Rakefile and took the opportunity to clean it up. In a small amount of time I created some code that I was reasonably happy with but again worried about having to drag a gist of code from project to project. I thought about future me and I wanted to ensure I did not have to remember any of this every again unless I absolutely needed.

Solution

It was then I went on the search for software to save me. It was then that I stumbled upon Rayh’s xcoder. I was able to throw away 200+ lines of Rakefile to more elegantly write the following:

Rakefile
1
2
3
4
5
6
7
require 'xcoder'

task :default => :specs

task :specs do
  Xcode.project('TestProject').target('Specs').config('Debug').builder.build
end

Gorgeous! And more importantly exceedingly more portable when I want to get started on the right foot. To make this ever more portable I recently added default rake tasks to the xcoder gem that adds all your Xcode projects in your default directory available through rake.

Rakefile
1
2
require 'xcoder/rake_task'
Xcode::RakeTask.new

Which will generate several common tasks for each of your projects > targets > configurations

rake output
1
2
3
4
5
6
7
8
rake xcode:test_project:specs:debug:build                   # Build TestProject Specs Debug
rake xcode:test_project:specs:debug:clean                   # Clean TestProject Specs Debug
rake xcode:test_project:specs:debug:package                 # Package TestProject Specs Debug
rake xcode:test_project:specs:debug:test                    # Test TestProject Specs Debug
rake xcode:test_project:specs:release:build                 # Build TestProject Specs Release
rake xcode:test_project:specs:release:clean                 # Clean TestProject Specs Release
rake xcode:test_project:specs:release:package               # Package TestProject Specs Release
rake xcode:test_project:specs:release:test                  # Test TestProject Specs Release

Now the Rakefile looks something like:

Rakefile
1
2
3
4
require 'xcoder/rake_task'
Xcode::RakeTask.new

task :default => "xcode:text_project:specs:debug:build"

The benefit is that Xcoder is leveraging the project file to generate the correct xcodebuild operation allowing your build to remain in lock-step with the Xcode environment as you build and test your product in a headless environment.