Chapter 2. Concurrent Development with iOS

Enterprise application development at most companies is usually about teams, often large teams. The Agile movement has done little to change this tendency, and in fact embraces the idea of team development, with a backlog of stories that are apportioned out to the pool of developers on the team on a sprint by sprint basis.

So what does this mean for iOS applications development? Well, if you’re a Java programmer working in Eclipse, you can split up development pretty easily. Eclipse is largely directory based, and the Eclipse project file is fairly stable. Xcode…not so much. Xcode is the obsessive-compulsive poster child of IDEs. It wants to manage every single file at a micromanager level, and it’s not enough to drop new files into a directory, you need to tell Xcode to use them in specific build targets, and all of that info is stored in a single xcodeproj file.

If you’re not very careful when creating new files in Xcode, you tend to end up with everything in one flat directory. Using groups can give structure to the project view inside Xcode, but it does nothing to organize the physical file system layout. And as you may have already discovered, trying to go in after the fact and move files around on disk is messy, because there’s no way to notify Xcode that you’re doing it. The best I’ve ever done is to delete the references (which turn red when you move the files), then re-add them and hope that you haven’t screwed up your build manifests in the process.

A Little Ditty ‘bout Tom and Diane

Let’s look at a very small project with two developers, and see how things can get messy very quickly. We’ll be developing a social networking tool throughout the book, and our two developers (Tom and Diane) are going to start with the login screen. Tom is in charge of UI, and Diane with backend integration. Unfortunately, Tom and Diane work for BuggyWhipCo, which is still using (shudder) CVS as the source management platform, which isn’t supported natively by Xcode. As of this writing, Xcode only supports git and Subversion natively in the UI, so our intrepid pair is going to have to check their code in manually on the command line. This is not at all uncommon—git is extremely rare in corporate settings, although SVN is becoming more popular. Many companies are using proprietary tools from companies such as IBM. The source control system at the company I spend my days coding for uses a source control system used by so few companies that telling you the name of the tool would literally let you figure out where my day job is.

Tom begins by creating an CVS repository for the project, and then fires up Xcode, and creates a new project using the Xcode project wizard, specifying a master-detail style application (see Figure 2-1).

Creating the BuggyWhipChat project
Figure 2-1. Creating the BuggyWhipChat project

Before he does anything else, he checks in the project by importing it into CVS:

Tom$ cvs import buggywhipchat buggywhipco v1-0
.
.
.
No conflicts created by this import
Tom$

Tom gives Diane the location of the new repository, and she checks out the project to start adding some backend server integration. She starts by creating an stub class that she plans to check in immediately, so that Tom can start coding against the interface, even before Diane has implemented it. She uses Xcode to create some Twitter API methods (don’t panic, we’re not going to try to implement a real Twitter interface in this book; it would take up the whole thing!) Figure 2-2 shows what her Xcode environment looks like after creating a class, in a new group called APIs.

The project view after adding Diane’s new files
Figure 2-2. The project view after adding Diane’s new files

Meanwhile, unbeknownst to Diane, Tom has begun working on his UI design. In support of it, he has created a splash screen using an XIB file. His project view is shown in Figure 2-3.

Tom’s project with the new XIB file
Figure 2-3. Tom’s project with the new XIB file

At this point, everything is in place for hilarious hijinks to ensue. Let’s say that Diane checks her new code into CVS first:

Diane$ cvs add BuggyWhipChat/BuggyWhipChat/TwitterAPI.*
cvs add: scheduling file `BuggyWhipChat/BuggyWhipChat/TwitterAPI.h' for addition
cvs add: scheduling file `BuggyWhipChat/BuggyWhipChat/TwitterAPI.m' for addition
cvs add: use `cvs commit' to add these files permanently

This isn’t the whole story though, because if Diane commits now, she’ll be also committing changes to a local file that got silently modified when she added those class files to Xcode. A cvs status invocation tells the whole story:

cvs status: Examining BuggyWhipChat/BuggyWhipChat.xcodeproj
===========================================================
File: project.pbxproj  Status: Locally Modified

That pesky project file has also been modified, since it keeps track of which files are associated with which targets in the project. If Diane did not commit the changes to the file, anyone else checking out the project won’t see the new class in their project view, even though the files will physically be on the disk.

Let’s say that Diane has done this enough times that she remembers to commit the project file. We’re not out of the woods yet! What’s going to happen when Tom wants to commit his new XIB file? Being an experienced CVS user, he’s going to start by doing a cvs update:

cvs update: Updating buggywhipchat/BuggyWhipChat/BuggyWhipChat.xcodeproj
1.1.1.1
retrieving revision 1.2
Merging differences between 1.1.1.1 and 1.2 into project.pbxproj
rcs merge: warning: conflicts during merge

Well, that’s no fun at all…not only do you end up with a merge conflict, but because of the way CVS does merges, you end up with a broken project file (which is likely to crash Xcode if you happen to have the project open at the time you do the merge). Opening the file reveals the conflicts, which will occur in several sections. Here’s an example (the lines have been truncated for readability):

<<<<<<< project.pbxproj
1A05AE0613BB5F880080AAD4 /* SplashScreen.xib */ = ...
=======
1A05AE1213BB63210080AAD4 /* TwitterAPI.h */ = ...
1A05AE1313BB63210080AAD4 /* TwitterAPI.m */ = ...
>>>>>>> 1.2

So, Tom is going to get to edit his project.pbxproj file by hand, looking for the conflicts and resolving them. In this case, it’s pretty simple, because he can just take the changes made from both sides, ending up with the project view shown in Figure 2-4.

The merged project view
Figure 2-4. The merged project view

More Merge Mayhem

“OK,” you may be saying to yourself, “It’s messy, but I can deal with doing some merging every time someone adds new files to the project.” But adding files is just the beginning of your troubles. The project file is changed whenever you change the build target in Xcode. It gets changed if you update your provisioning profile. Adding a group, moving files between groups—you name it, and it somehow is going to mess with the project file.

You can end up with all sorts of disasters if you merge the project file incorrectly, from broken builds to projects that won’t open at all inside Xcode. They are far from human-readable, although once you’ve had to deal with enough merge issues, you start to get the lay of the land a bit.

This isn’t some abstract peril. Because of the way my company uses Agile development, I may be working on multiple headlines at the same time, and I’m not allowed to promote the code associated with a headline to the QA branch until the headline is complete. As a result, I frequently have multiple sub-branches of the project living in separate checked out copies of the workspace on my desktop, and promote and merge as appropriate. If I had a dollar for every time that I broke the parent build by mis-merging the project files, I could buy myself a nice dinner.

But at least there’s a chance that you can merge project files, since they’re vanilla XML. Let’s talk about a real nightmare: language translation files. The espoused best practice when doing iPhone development is to use NSLocalizedString to do all your string lookups, and then let genstrings autogenerate the translation resource files (which are all bundled together under the Localizable.strings file in your Xcode project). As experienced iOS developers learn, genstrings overwrites the old translations every time you use it, so there are a number of publicly available tools that will do a more graceful regeneration and merge. My tool of choice is the Python script localize.py, available at http://github.com/joaomoreno/Green-Apples/raw/master/localize.py.

As long as you only have a single person dealing with this file, you’re OK. But once you have multiple people all adding strings, or even multiple branches (as in my situation with multiple branches for different headlines), you’re sunk! The big problem you’re going to run into is that the translation files are UTF-16 files, and most source control systems blithely look at them, and say “Oh, that’s a binary file; I have no idea how to merge it.” So if you have multiple people messing around with your translation strings, you can end up with a very painful manual merge (or have to use a tool such as FileMerge, which knows how to handle UTF-16 files).

In general, any machine-generated iOS file (XIB files, core data schemas, etc.) is going to be a headache to merge if you have more than one person mucking with them at the same time, and the cost of doing it wrong can be very high. So how do you handle multiple engineer development without going prematurely grey?

Workspaces and Static Libraries

One of the major advancements that Xcode 4 brought with it was the concept of a workspace. You can think of workspaces as a project composed of projects, and one of the big benefits of this approach is that each project tracks its files and other settings separately. What this means is that Diane can add classes to her API project, and Tom can merrily design UI, without stepping on each other’s projects.

You can create a new workspace-based project by simply selecting File→New→Workspace, but assuming that you’ve already got a project underway, you can turn your existing project into a workspace-based project by doing File→Save as Workspace... Once you have saved your project as a workspace, you can open it by selecting the xcworkspace file using File→Open.

As I mentioned, the big advantage of using workspaces is that you can create multiple projects under the workspace, and have the main project set dependencies on the “child” projects, so that they are automatically built and linked in to the main project. But because they use independent project files, developers can work on them individually without creating merge issues.

Let’s assume that Diane did just that, and turned her project into a workspace. In addition, she created a new project inside the workspace called ChatAPI. To do that, all she needed to do is to choose File→New→Project, and once she was at the new project wizard, select a Cocoa Static Touch Library under the iOS Framework & Library section. She named it ChatAPI, and placed the project directory at the same level as the main BuggyWhipChat workspace file (see Figure 2-5).

Creating a project inside a workspace
Figure 2-5. Creating a project inside a workspace

When she’s done, she ends up with a project navigator view that looks something like the one in Figure 2-6.

The project view of a workspace with a subproject
Figure 2-6. The project view of a workspace with a subproject

What this looks like on the file system, in an abbreviated form, is this:

./BuggyWhipChat
./BuggyWhipChat/BuggyWhipChat
./BuggyWhipChat/BuggyWhipChat/DetailViewController.h
./BuggyWhipChat/BuggyWhipChat/DetailViewController.m
./BuggyWhipChat/BuggyWhipChat/RootViewController.h
./BuggyWhipChat/BuggyWhipChat/RootViewController.m
./BuggyWhipChat/BuggyWhipChat/TwitterAPI.h
./BuggyWhipChat/BuggyWhipChat/TwitterAPI.m
./BuggyWhipChat/BuggyWhipChat.xcodeproj
./ChatAPI
./ChatAPI/ChatAPI
./ChatAPI/ChatAPI.xcodeproj
./ChatAPI/ChatAPI.xcodeproj/project.pbxproj

Diane also needs to make the main project depend on the new ChatAPI project. There are a few ways to do this, but probably the easiest is to select the BuggyWhipChat project, go to the Build Phases view, and add ChatAPI as a target dependency. Xcode is supposed to figure this out automatically, but I’ve had hit-or-miss luck with it, so I prefer to do it explicitly. While she’s there, she also has to add the library archive from the subproject as a linked library for the main project (see Figure 2-7).

Making the main project depend on the subproject
Figure 2-7. Making the main project depend on the subproject

Also, somewhat annoyingly, Diane has to manually add a relative path to the ChatAPI directory to the Header Search Paths build setting for the parent project, so that the parent can reference any new header files that Diane creates after the split. Or, alternatively, she can copy the header files into the main project, although this will get you back into the scenario of having two people modifying the same project file.

The final step is to move the TwitterAPI source and header into the new subproject, and remove the references from the main project. Because Xcode associates projects and groups with physical location on disk, they’re going to still be in the BuggyWhipChat directory. If you’re obsessive (or your company is) about directory organization, you’d probably want to move the files physically to the correct directory, and use Add Files... to recreate the references in the ChatAPI project.

Diane can then check her new workspace and project into CVS by adding all the new files and commit the other ones. Tom does a CVS update, and importantly, reopens the project using the workspace file. Now both Tom and Diane are working inside a common workspace, but on different project files.

So what happens if Tom and Diane both independently create new files now? As an example, suppose that Diane adds a new Objective-C class called GoogleTalkAPI, while Tom creates a new UIViewController-based class called ChatChooser, complete with an XIB file. This time, Tom is the first one to add and commit his new files. When Diane does a CVS update, here’s what she sees (in abridged form):

Dianescvs Diane$ cvs update buggywhipchat
cvs update: Updating buggywhipchat/BuggyWhipChat/BuggyWhipChat
U buggywhipchat/BuggyWhipChat/BuggyWhipChat/ChatChooser.h
U buggywhipchat/BuggyWhipChat/BuggyWhipChat/ChatChooser.m
U buggywhipchat/BuggyWhipChat/BuggyWhipChat/ChatChooser.xib
cvs update: Updating buggywhipchat/BuggyWhipChat/BuggyWhipChat.xcodeproj
U buggywhipchat/BuggyWhipChat/BuggyWhipChat.xcodeproj/project.pbxproj

The files that Tom modified (the project file, when he added new classes) and the new class files and XIB have shown up. Notice that even though Diane has added new files to her project as well, there’s no conflict, because those files were added to the project file for the ChatAPI project. In fact, once she adds and commits her files, Tom will get a similar experience the next time he updates:

buggywhipchat Tom$ cvs update
cvs update: Updating ChatAPI
cvs update: Updating ChatAPI/ChatAPI
U ChatAPI/ChatAPI/GoogleTalkAPI.h
U ChatAPI/ChatAPI/GoogleTalkAPI.m
cvs update: Updating ChatAPI/ChatAPI.xcodeproj
U ChatAPI/ChatAPI.xcodeproj/project.pbxproj

No muss, no fuss, no broken project files. This strategy works well for dividing up work, with a few caveats.

Make Sure All Dependent Projects Do Their Own Unit Testing

Xcode will offer to create a Unit Test target for a library, when you create it. You should take Xcode up on the offer. By its nature, a dependent library needs to be able to stand alone, and that includes testing.

This is pretty straightforward if all your library does is implement business logic, or something else divorced from the UI. It becomes more troublesome if you are going to try, for example, to divide up the UI responsibilities for the applications into various subprojects, and then integrate them all up into a main project. In iOS 5, you can do business level testing by injecting your unit tests into the running application, but in this case, you need to have an operating UIApplicationDelegate. Where are your subprojects going to get it? Are you going to have a dummy main.m in each of your subprojects that fires up the application just far enough to test the UI code? These are the kinds of issues you need to consider when you start dividing up your project.

Also, as mentioned in the next section, XIBs and other resources can’t live in libraries, so the ability to divide up UI work into multiple developers is fairly limited to begin with.

You Need to Plan Out Common Resource Issues

Unfortunately, breaking projects up into subprojects doesn’t solve all your problems. In fact, it doesn’t solve many of them. For example, there’s no way in a static library to include bundle information, such as localizable strings and images. You need a framework to do that, and at the moment, Apple doesn’t allow custom frameworks for the iOS family of products. Maybe sometime in the future, this will be available, but for the moment, you’re fairly much screwed if you want to keep your localizations close to the library.

This doesn’t mean that you can’t take advantage of NSLocalizedString in your libraries, just that the actual Localized.strings files are going to have to live in the main project. And as I mentioned, those files are a real bear to merge, because they are UTF-16 format. The best strategy is going to depend on the dynamics of the group, but it probably makes sense to appoint a single “stringmeister,” who is responsible for the overall coordination of the string files. This person can also serve as a liaison to the translation resources (in-house or contracted) that are actually going to do the work.

This is also a good practice because, in the absence of a single person riding herd on the strings, you tend to end up with duplication all over the place as multiple developers add strings such as “OK” and “Cancel.” It is, however, a thankless job, so it’s probably a good idea to rotate the work around to the various members of the team.

The biggest problem you’re going to face is that you can’t put XIB files in your libraries, because only framework libraries can hold this kind of data, and Apple reserves to itself the ability to create frameworks in iOS.

You Can Still End Up Stepping on Each Other’s Feet

The other headache you can run into is that, at the end of the day, there are still some code paths that everyone is going to end up going through. For example, a main menu that leads to the various subfunctions of the application will need to be modified by each developer working on a subfunction, to enable access to that function. But this is not nearly as bad a problem as adding and removing files, because if you’re only editing a class file, you’re not modifying the project file, so you’re left with an ordinary merge, just like in any other language under source control.

However, it’s also possible to modify your project file without meaning to. Many activities that take place on the project view edit the project file under the table. For example, if you set NSZombieEnable, you’ve just tweaked the project file. If you check it in, you’re going to end up in merge mayhem. So, a general rule of thumb is, don’t check in your project file on a commit, unless you’ve added or removed files (or made some change to the project that needs to be permanent, such as adding a framework).

Luckily, a lot of the things you can change (such as the current build target) are stored in the user project file, not the project-wide file. These files live in the xcuserdata directory inside of xcodeproj. There’s a separate data directory for each user (Diane.xcuserdatad, for example), so you would be unlikely to create a conflict even if the files did get checked into source control, but there’s no reason they should be checked in at all, so don’t. In fact, if you’re lucky enough to be able to use XCode’s integrated SVN or git support, it won’t even offer to check these files in unless you force it to.

Let’s Be Careful Out There

Best practices are great, but they never take the place of diligence and planning. As hard as you try, if you have multiple people working on the sample iOS project, files will get broken, builds will fail, and work may get lost.

To some extent, these are the rules of the road when working on iOS. You don’t have Frameworks to let you cleanly divide up work, Xcode is highly dependent on machine-generated files to manage the project, and none of it was designed to facilitate team-based application development.

However, going in to the project knowing where the pitfalls are likely to occur is a big first step toward preventing problems. Nothing, however, takes the place of good communications between team members. Leverage those agile practices, mention during your standup if you plan to mess around with a frequently touched file. If there are geographic issues with your team, make sure you have procedures in place for good hand-offs, so your current state follows the sun as well as your code.

One method of catching problems you may have introduced (before they can fester) is continuous integration and automated builds. In the next chapter, we’ll walk through the challenges that build automation presents, and see how (relatively) easy it is to get out of the nightly build game.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset