Saturday, February 28, 2009

iPhone Development and Code Sharing

This post describes an elegant method for sharing code between iPhone applications. According to my knowledge, the techniques in this post are the first major breakthrough over manually configuring a static library, or building a SDK.
The post starts with an explanation of how I package code in an Xcode project, so that the code can be reused. The structure description is followed by a step-by-step guide of importing reusable code in another Xcode project. My process has the following advantages:
  • the reusable code is imported in the consumer Xcode project, so it is built together with the consumer code, and the developer has full browsing and debugging support
  • the reusable code that was imported into a project can easily be updated to newer versions
Update: I have open-sourced the iPhone toolkit that I used when writing this article. This means you can find an example packaged project structure here, and you can read more about the toolkit here
    Reusable Xcode Projects
    The picture on the right shows an Xcode project wrapping the code to be reused.
    First, notice how all the code has been tucked away under a single folder, with the same name as the project, which is ZergSupport for this example. This is because everything in the project will be imported directly into Xcode projects that use the code, and we don't want to litter their directory structure.
    The other important feature of this project is its targets. The code is grouped into three targets:
    1. All the "production" code that is supposed to end up on customers' iPhones is included into a static library with the same name as the project (ZergSupport).
    2. Code intended to help test the production code above is included into another static library. I named the library ZergTestSupport in my example, because I don't know an obvious naming convention. This library includes the main library (ZergSupport).
    3. Tests for all the code above are separated into another target, whose name is the project name plus the suffix Tests (in this case, ZergSupportTests). This target is an iPhone application, prepared according to the instructions in the Google Toolbox for Mac.

    The first two targets are static libraries. Create a static library target by going to Xcode's menu, selecting Project > New Target. Name your library, then select OK.

    In order to add files to the static library, first make it the active target, by selecting it from the top-left drop-down (see picture at the right). Then click on the folder holding all your sources in under Groups & Files (Xcode's project tree) and click the checkboxes next to the files that you want in your static library, as shown in the picture below and to the left.

    Having to separate sources for each target can be a pain, but it's well worth it. Here's why:
    • Shipping testing code in an iPhone application will increase its size. If the application goes above 10Mb, people cannot download it over a cellular connection, and need either WiFi or a computer. This means less customers. So separate test code from production code.
    • Some open-source licenses (e.g. GPL, BSD with advertising clause) tend to have more relaxed requirements if the code is only used in-house. Testing code meets this condition, as long as it doesn't end up in the binary that is sold. So separate test code from production code :)
    • People using your reusable code should not have to run your unit tests (and wait for them), if they don't modify your code. So separate test code from test-supporting code.
    Last, maintaining target membership can be simplified by using good naming conventions, and a tool I wrote. For instance, suppose you have two targets, CodeTargetName (for the code that ends up in the client) and TestTargetName (for the test cases), and all your test code is in files whose names end in Test (example: RssReader.m, RssReader.h, RssReaderTest.m). The following commands in your project folder will bucket your files correctly between your targets.
    $ zerg-xcode retarget . ".*" CodeTargetName
    $ zerg-xcode retarget . "Test\.m$" TestTargetName
    
    The commands above use zerg-xcode, a tool that is introduced below.
    Importing Reusable Code
    Fortunately, using code packaged in the manner described above is much easier than packaging the code.
    The instructions below assume that the library project (ZergSupport in my example) and the project that consumes it are in sibling folders. For example, I have all my Xcode projects in ~/xcodes so I would download the reusable project in ~/xcodes/ZergSupport and my application, which wants to use it, would be in ~/xcodes/MyApp. The following commands will perform the import, when issued from the directory of the consumer application (MyApp).
    $ sudo gem install zerg-xcode
    $ zerg-xcode import ../ZergSupport
    $ zerg-xcode addlibrary ZergSupport MyApp
    $ zerg-xcode addlibrary ZergTestSupport MyAppTests
    
    The first command installs the tool that does the importing. The second command adds everything in the ZergSupport Xcode project to the MyApp project, and copies all the necessary files. The last two command add the right library dependencies - my application will include the "production" code in ZergSupport, and my tests will include the testing helpers.
    Last, and probably most important - updating to a newer version of the reusable code is achieved by downloading the new version in the same place as the old one, and re-issuing the import command above. The importing logic will preserve dependencies.
    Conclusion
    Code reuse doesn't come easy in iPhone development, especially because frameworks are forbidden. This post presents a method that facilitates code reuse. I hope my work will facilitate the appearance of open-sourced components that will build up to an infrastructure of similar depth and quality as Rails.
    Recognition
    I learned about using static libraries on the iPhone in this awesome blog post. I used that as my starting point, and tried to automate the process as much as possible.

    1 comment:

    1. Hi Victor, you've got a great tool here--thanks for sharing! In addition to using zerg-xcode, there's another option that might be helpful in some situations. It involves using an Xcode "cross-project reference" which allows you to include the code and products of Project A inside Project B without copying the files. I took some time to write up a tutorial for this on my own blog. Maybe not perfect for every situation, but very useful sometimes.

      ReplyDelete