Benedict's Soapbox About Me Selected Work

24 January 2012Managing Large iOS Projects

I recently worked on a relatively large iPad app. Although this was a relatively small team, there was still potential for us to stand on each others toes. This post is a collection of rough notes and tips that describes the steps we took to stay productive. If you’d like more details on anything please get in touch.

Background

Let me explain what I mean by ‘large’. The app was ‘large’ due to the size and number of content files. The app was rather modest in terms of lines of code. There was a maximum of 3 people (2 developers and 1 designer) working directly with the main project or sub-projects at any time. Although this is a small team it’s still large enough to cause problems. In fact any project with more than one person has the potential for these work flow problems to occur.

Beware the Xcode project files

I’m going to start with a rant. I get the impression that the Xcode project format was not built with multiple users in mind. An Xcode project is a bundle that contains files for user specific settings (break points, Xcode window layout etc) and 1 monolithic file, project.pbxproj, that references all the files in the project and build settings etc. project.pbxproj is a JSON-like format which and is not meant to be edited by humans and it does not play well with auto-merging algorithms used by source control. The upshot of this is that this file often becomes the victim of painful source control conflicts when there’s more than 1 person working on a project. Such conflicts are hard to resolve because unlike changes to actual source files the changes to project.pbxproj are performed implicitly by Xcode rather than directly by the user. Yes, these problems can be mitigated with meticulous use of source control, but having to do so feels like the user is supporting the tools instead of the tools supporting the user. (A possible solution would be to take advantage of the fact that the project is a bundle and split project.pbxproj into multiple files; a file for the group structure, files for the build settings for individual files, files for target settings etc. This would make also it easier to see what’s changed when using source control.)

Due to the horribleness of the Xcode project it pays to organise your project so that the number of people working on an individual project file is kept to a minimum.

Workspaces and sub-projects

In Xcode 3 it was possible for a project to contain a reference to other projects; this is achieved in the same way as adding an existing file to the project. I refer to these as sub-projects (I think the proper name is ‘external project reference’). Xcode 4 introduced improvements for working with multiple projects. Apple attributes the improvements to a new featured called Workspaces. Workspaces allow multiple top-level projects in the Xcode organizer. However, the improvements attributed to workspaces also apply to sub-projects. The difference between workspaces and sub-projects is that a project can incorporate targets from sub-projects into its build process, but a sub-project cannot incorporate targets from it’s ‘parent’ project while projects in a workspace can all incorporate each others targets.

Breaking a project down into multiple projects reduces the number of people working on any given project. Use the 6 Principles of OOD relating to packages to help decide what each sub-project should contain.

Gotcha: Beware the ‘install phase’

The ‘Skip install’ build setting causes problems when archiving an app. With the exception of the main target all targets must have ‘Skip install’ set YES (even if the product of the target is not included in the main product). The default value is NO.

Code in sub-projects (static libraries)

Static libraries allow code to be moved into sub-projects. I’m not going to describe how to create static libraries because others have done so already. Using Open Source Static Libraries in Xcode 4 tells you (almost) everything you need to know. However, there are a few extra things I’d add:

  • Start with a standard iOS app and add a static library target to it. Having an app to play around with makes it easier to develop.
  • Gotcha: By default categories implemented in static libraries are not linked. To remedy this add the -all_load linker flag to the main project. Technical Q&A QA1490.

Non-code assets for static libraries

There are occasions when static libraries require assets, e.g., images and sound files. Sub-projects and projects in the same workspace share a build directory and Xcode manages the build order of each project so that the main target is always built last. Because of this we can add build phases to our main project and sub-project which copy static library assets into the main bundle:

  1. In the sub-project target add a ‘Copy Files’ build phase. Set the Destination to ‘Products Directory’ and the subpath to MainBundleAssets. Add the assets to this build phase.
  2. In the main target add a ‘Run Script’ build phase:
    cp -rf "${BUILT_PRODUCTS_DIR}/MainBundleAssets/"* "$CONFIGURATION_BUILD_DIR/$CONTENTS_FOLDER_PATH/"
    This will copy the files from MainBundleAssets into the main bundle.

Resource Bundle sub-projects

Bundles allow arbitrary resources to grouped together and treated as one entity. They’re used for many purposes in Mac OS X and iOS. Many OS X apps store documents as bundles; the document bundle is effectively a bunch of assets and the app the tool for working with those assets. It’s useful to make this same distinction for an iOS app. Design the app so that it loads a resource bundle. It’s an example of designing to an abstraction rather than the specifics.

Some general tips for how to organise and handle a large quantity of assets:

  • Use a naming convention so that each asset can be identified at runtime.
  • When in development ensure that there are no missing assets by crashing early and often.
  • Maintain a sane directory structure and don’t rely on Xcode groups (moving a file from a group doesn’t move it on the disk – this can result in things getting messy).
  • It’s worth remembering that nib files can be used to instantiate arbitrary objects.
  • Gotchas: Beware duplicate file names. It’s possible to have 2 files with identical names but in different directories. But when the bundle is built all files are copied into one folder. This results in 1 of the files being copied over.

Improve performance by moving run time operations to compile time

To improve performance move expensive operations from run time to build time and cache the result. This can be achieved by creating an OS X ‘Command Line Tool’ target that runs as part of the bundles build process. Core Data can be used to store the data created at build time (Core Data is cross platform so a Core Data store that is created on OS X can be read by iOS).

Build tools that allow content to be tested quickly

The best way to test content is to see it in place within the app. However, compiling an app takes time which means that viewing the content in place can be slow. What’s needed is a quick way to test the content without compiling the whole app. One solution is to create a testing app based on the main app. Add features to the testing app that aren’t required in the final app (e.g. re-loading the resource bundle at run time and provided onscreen info that’s useful to the designer). Tips for creating a testing app:

  • Create the testing app by duplicating the app target and give it a distinct name (e.g. ‘ContentTester’).
  • Add categories and method swizzling to key classes (app delegate, view controllers etc) to augment functionality. Add these in separate source files so that the main app stays free of any changes.
  • Add a mechanism for loading content at runtime (e.g. responding to the shake gesture, which is easy to trigger in the simulator).
  • Add macros to ensure that this code never gets compiled into the final build.
  • Restrict the test app to the iOS simulator.
  • Load the content from a place on the local system (e.g. /tmp) so that the content can be reloaded without rebuilding the app.
  • Use symlinks to avoid having to copy files.

1 Comment

Make a Comment

:

:



Timeline