Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4384

Navigating a New Codebase: Tips and tricks for getting up to speed quickly

$
0
0
Learn how to navigate a new codebase effectively.

Learn how to navigate a new codebase effectively.

Note from Ray: This is a brand new tutorial released as part of the iOS 8 Feast. Enjoy!

Learning a new codebase can be a daunting task. There are typically many, many parts in any codebase and it can feel overwhelming to understand how everything fits together. After reading this tutorial you’ll be navigating a new codebase effectively.

This tutorial shows you how to expedite the process by offering a selection of techniques that help you associate what you see on the screen with what’s happening in the code. These tricks all have the same goal but work in different ways; you’ll learn how to choose the right tool for the job.

Since this tutorial works with a real-world codebase for an app currently in the App Store, it includes no Swift files. You can take a break from the onslaught of Swift articles out there and work with some Objective-C for a little while.

Note: Exploring code largely relies on debugging. You should have at mimimum a basic knowledge of how to debug your application and use the lldb console. If you need to get up to speed on this, check out Intermediate Debugging With Xcode 4.5 or the debugging video series.

Getting Started

In this tutorial you’ll take on the role of a new hire at Wikipedia, who has a few tickets to address, which are challenges to fix bugs or implement feature requests.

You’ll working with the iOS Wikipedia App, which is an open source repo. This tutorial uses a forked copy which can be found here.

Don’t download a zipped version of the repo; instead, clone it using using your favorite git application or Terminal. If you aren’t familiar how to clone a repo, enter the commands below in Terminal, one at a time:

$ mkdir WikipediaApp 
$ cd WikipediApp
$ git clone git@github.com:DerekSelander/apps-ios-wikipedia.git
$ cd apps-ios-wikipedia
$ open Wikipedia.xcodeproj

Make sure you’re on the master branch — this should be selected by default. To do this, first run the following command:

$ git branch

Check in the output that the asterisk is next to master. It should look like this:

* master

If it is not, then run the following command:

$ git checkout master

Re-check that you’re on the master branch after running this if you wish!

Note: As of this writing, 4.0.1 is the latest Wikipedia App version in the App Store. By the time you read, the version of the Wikipedia app might have changed. The version of the forked repo you’re using in this tutorial, however, will stay frozen at 4.0.1 and won’t contain any future updates from the app’s developers.

Your first task when learning a new codebase has little to do with the code itself — instead, you should learn how the app is structured and how it functions.

Exploring the App

Open the app in Xcode and run it up in a simulator. Without looking at the code, work your way through the app and discover how to navigate to each of the screens shown below:

ScreensList

You’re really just trying to get a feel for how the app functions and how a user would experience the app. While you’re navigating around the app, ask yourself the following questions:

  • How many distinct screens are in the app?
  • If you had to name the screens, what would you name them?
  • How complex are the screens; that is, how many different kinds of UI controls does the app use?
  • Which screen uses the greatest variety of controls?
  • What is the navigational flow that connects one screen to another?
  • Which UI controls and navigation mechanisms are standard to iOS, and which ones appear to be custom or unfamiliar?

The point of these questions is not to answer them all at once. Asking these questions helps you notice far more about the app than if you just absentmindedly swiped through the screens. Exercises like this help you quickly build a mental model of the app based on its visible features such as interface components and UI behaviors.

To complete your mental model, you’ll need to link the visible features of the app to the invisible features of this app — that is, the codebase itself — by investigating the app’s classes, images, and other code-level resources.

Since a real production app like Wikipedia can easily involve hundreds of files, it’s essential to master the Xcode features that let you navigate quickly through the codebase. Otherwise, it’s like trying to learn your way around a new city by crawling around on your hands and knees, examining the streets with a magnifying glass! :]

Navigating around Xcode

Here’s a quick refresher on Xcode’s navigation features:

  • To jump to the location of a method or property definition: ⌘+Click the item with the mouse, or place the cursor somewhere inside the method name and press Ctrl+⌘+J using the keyboard alone.
  • To jump back to where you were, press Ctrl+2 to show the previous history list and choose the first item in the list. An even shorter key combo is Ctrl+⌘+left or a three finger swipe, provided you haven’t already dedicated those shortcuts to Mission Control. This is configurable via System Preferences / Keyboard.
  • Want to find the corresponding Nib/Storyboard file for the current UIViewController? Press Ctrl+1, type User Interface and press Return and then Return again. This is a killer shortcut if you need to know whether the view controller was implemented directly in the code or via Interface Builder. In addition, if you hold option while opening the file, then it opens in the assistant editor.control+1
  • To jump to a particular filename method, press ⌘+Shift+O and type the name of the item you’re looking for.cmd+shift+o
  • To get a bird’s-eye view of all the methods in a file, press Ctrl+6 to bring up a list of all the methods (as shown in the image below), then begin typing to narrow the selection.control+6
  • To jump to a specific line number, press ⌘+L and the line number.cmd+l

For more tips and tricks with Xcode, check out the Supercharging Your Xcode Efficiency tutorial.

It’s time to get down and dirty with the codebase — the most logical place to start is with the various screens of the app and their associated UIViewControllers.

Seeing how Views are Assembled

When view controllers are sensibly named, you can pretty much guess where they appear in the UI and how they’re created. View controllers can be created from NIBs, from Storyboards, or entirely from code. A primitive yet effective way to find the view controllers is to search the codebase for these assets and inspect the results.

Ensure you’re in the Project Navigator tab; if not, press, ⌘+1 to get there quickly. In the lower left corner of the Project Navigator panel, type .xib into the search field and a handful of NIBs will show up. Take a look at each one in Interface Builder; these NIBs seem like they’re designed for UIViews or UITableViewCells, not UIViewControllers.
thesearenttheviews

Perhaps the views you’re looking for live in Storyboards instead. Enter .storyboard in the search bar. Only one item shows up: Main_iPhone.storyboard. Open that file — is there anything of value inside?

Jackpot! This Storyboard houses the majority of the UIViewControllers in this app. Revisit this file if you need to associate a given view controller in the UI with it’s class.

Finally, go to the find navigator (⌘+3) and search for ViewController. Since this codebase has a regular naming convention, the search results highlight code with numerous view controller subclasses. These results don’t provide the valuable instant navigation overview of a Storyboard file, but they might help you never-the-less.

Mapping the Code With Symbolic Breakpoints

Now that you have a basic understanding of the view controllers used in this app, your next step is to see which view controllers pop up when the app runs.

Open up the Debug Navigator — ⌘+7 is a handy shortcut. Click on the + at the bottom left of the screen and then select Add Symbolic Breakpoint…. Type in viewDidLoad; this places a breakpoint in every object that holds a viewDidLoad method.

The image below shows you how to set up this breakpoint.

SymbolicBreakpoint

This breakpoint will help you map which view controllers run when you exercise the app. Every time a view controller loads its view, the app will pause execution and you’ll be able to see the view controller’s name in the main thread stack in the Debug Navigator.

If the view is defined in the code itself, you’ll see the code itself in the editor pane; when it’s Apple’s own framework code, as happens in every call to the UIViewController superclass, you’ll only see an assembly listing.

Launch the app again with the symbolic breakpoint enabled. Although your progress through the app will be a lot slower, this is a good way to get a list of view controllers used when the app starts. If you get tired of hunting for the button to continue debug execution, use Ctrl+⌘+Y to speed things along.

What view controllers pop up when you run the app? Check your results against the list below.

Solution Inside: Solution SelectShow>
Note: The Facebook Chisel library provides a much faster way to build these lists via the pvc lldb command. Check out Using LLDB in iOS Part 8: Using Chisel to learn how to use Chisel.

Now that you’ve gained a bit of knowledge about the app, you can tackle your first ticket!

Ticket #1

Bug: The navbar displays content underneath the status bar when saving content.

  1. Navigate to a wiki page. Press the edit button in any section.
  2. Change some content in the textview then press the Next button.
  3. Content appears underneath the status bar like so:

navbarbug
QA Helper note: this problem is due to the prefersStatusBarHidden property of UIViewController. Because it is currently set to YES, the view controller is positioning the controls incorrectly. Override this method to NO in the appropriate view controller.

For this particular case, you know from the detailed ticket exactly what to do; you just don’t know where to do it. You could, of course, find the view controller’s name by examining the Storyboard, but that would take too long. Instead, you should let the debugger notify you when the appropriate UIViewController loads — that’s your clue that this is probably the view controller you need to modify.

Follow the reproduction steps in the ticket; run up the Simulator and edit a page, but stop just before you press the Next button. Make sure the viewDidLoad breakpoints are enabled. If you don’t want to slog through all of the breakpoints, you can use ⌘+Y to toggle all breakpoints on and off.

Click the Next button in the Simulator, and wait for the debugger to hit the breakpoint. You’ve stopped on a view controller, but is it the right one?

Press Ctrl+6 to bring up the function list, type in prefersStatusBarHidden then press Enter. You’ve stopped at the CaptchaViewController, but this view controller’s prefersStatusbarHidden already returns NO — you’re not there yet.

Continue execution to the next breakpoint. Once you reach PreviewAndSaveViewController, check prefersStatusbarHidden. Does it return YES? Yup, it does — you’ve found the culprit.

To fix the issue, replace the prefersStatusBarHidden method with the following code:

- (BOOL)prefersStatusBarHidden
{
    return NO;
}

Build and run your app; test your changes to make sure that your changes worked as expected. Go through the same steps that you went through when reproducing the bug. You should now see something like this on the final screen:

iOS Simulator Screen Shot 19 Sep 2014 09.41.07

Note: In no way do the patterns, design, or code of the Wikipedia app reflect upon the best practices taught on this site. It does, however, present an incredibly interesting example that is both successful and live in the App Store.

Symbolic breakpoints can really shine when you are trying to find a known method of an unknown class. Good knowledge of the iOS SDK and delegate methods can help you quickly hunt down an item of interest in a new codebase.

In addition, symbolic breakpoints can be surprisingly helpful when debugging properties. For instance, a property called isHidden has both a getter and a setter. You can instruct the debugger to stop only when the property is set by using setIsHidden:. This is ideal for classes whose instances interact with many different classes — such as singletons — and that have public properties which are being modified.

You’ve learned how to find the code behind a view using symbolic breakpoints. Next, you’ll learn how to do the same thing through inspecting the view itself.

Hunting for UIViews and Their Subclasses

Here’s the next ticket in your queue:

Ticket #2

UI Improvement: Change the color of the contents helper background from black to blue.

Make sure to use the predefined UIColor helper macro WMF_COLOR_BLUE (defined in WMF_Colors.h) when setting this particular color. In addition, change the bottom color from light gray to black.
BackgroundColorFeature

Sometimes symbolic breakpoints don’t really help that much. You don’t know if the color above is set in Interface Builder or through code. Furthermore, if it is being set in code, you can’t really rely on a setBackgroundColor: symbolic breakpoint as this could be set through Core Graphics APIs. Trying to visually pair the code with the correct UIViewController in a storyboard isn’t really a viable option as it would be incredibly tedious given the size of the Wikipedia Storyboard file.

The best approach in this case is to find the name of the class which houses the color you want to change and work back from there. Knowing the name of the class helps you as you search through the .m file, and if it’s not found there, can still help you reduce time spent looking for the appropriate item in Interface Builder.

Both Reveal and View Debugging in Xcode 6 can help you associate what is happening on the Simulator or device with the UIView hierarchy in a visual manner. Although Reveal is a much better product, in this tutorial you’ll take advantage of the free Xcode 6 View Debugging feature.

Run the application in iOS 8 (device or simulator). Disclose the side menu by tapping the upper right navbar button with three uneven horizontal lines. Once it’s visible, go to the Xcode menu bar and choose Debug\View Debugging\Capture View Hierarchy. This suspends the process and displays the view hierarchy head-on. It will look something like this:

View debugging

Click and drag to re-orient the view hierarchy into an exploded view. If you need to, use the provided controls to zoom in and out, pull the views farther apart, and filter the hierarchy to see only the subviews above or below a certain depth.

Exploded view debugging

Provided that UIViews are subclassed, clicking on the views themselves will bring up the corresponding class names. Search through the views, and find the one you want to change — it has a black background color. Once you’ve located the view, press ⌘+Option+3 to ensure you’re using the Object Inspector.

viewdebuggingscreen

  1. The view to search for and click on.
  2. The object inspector tab.
  3. The view’s class name and address of the instance.

The view’s name is TOCSectionCellView, so open TOCSectionCellView.m and see if you can find the logic that sets the background color in this cell and change it to WMF_COLOR_BLUE. You can check your work against the solution below.

Solution Inside: Solution SelectShow>

Build and run your application; follow the reproduction steps in the ticket to see that your changes worked.

Fixed ticket #2

It looks like you can use same View Debugging trick described to hunt down the bottom gray portion that needs to be changes. Or can you?

Try it — you’ll quickly find out that you’re looking at a view that belongs to a UIViewController, and as a result, is nameless since there is no subclass for the UIView itself.

As you can see, hunting down UIViews might not be enough. This is when you’ll want to find the UIViewController associated with the UIView in the view hierarchy.

Debugging View Controllers Using Method Swizzling

Since there is no reference linking the UIView back to the corresponding UIViewController subclass, you can’t look this up directly via the debugger even though you’ve identified the exact UIView instance of interest. What you really need here is for every UIView to have a property that points to its view controller, and for every UIViewController to initialize that property on its view.

Fortunately, this is completely achievable! You can create a new property for an instance of UIView using an Associated Object and inject the initialization logic for that property into one of UIViewController’s instantiation methods using Method Swizzling.

This results in all UIView references having a property that points back to the original UIViewController. Although all UIViews will inherit this property, it will only be initialized for those UIViews that belong to a UIViewController.

Note: Detailed explanations of Associated Objects and Method Swizzling are outside the scope of this tutorial, but if you would like to learn more about them, Matt Thompson (the AFNetworking dude) wrote some excellent articles about Associated Objects and Method Swizzling.

To solve this problem, you’ll implement a UIViewController category to show you the corresponding UIVIewController.

In Xcode, click File\New\File\iOS\Objective C File and then click Next. Name it DebuggingViewInjector. Enter Category for the File Type and UIViewController for Class. Also ensure you check the Wikipedia target when prompted to make sure this category gets compiled into the binary.

Open UIViewController+DebuggingViewInjector.m and replace its contents with the following code:

#import <objc/runtime.h> // 1
#import "UIViewController+DebuggingViewInjector.h"
@implementation UIViewController (DebuggingViewInjector)
 
+ (void)load { // 2
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ // 3
        Class class = [self class];
 
        SEL originalSelector = @selector(viewDidLoad); // 4
        SEL swizzledSelector = @selector(customInjectionViewDidLoad); // 5
 
        Method originalMethod = class_getInstanceMethod(class, originalSelector); // 6
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
 
        method_exchangeImplementations(originalMethod, swizzledMethod); // 7
    });
}
 
- (void)customInjectionViewDidLoad // 8
{
    self.view.debugVC = self; // 9
    [self customInjectionViewDidLoad]; // 10
}
 
@end

Here’s what’s going on in the code above:

  1. Make sure you import the runtime headers used to perform this dark and powerful sorcery.
  2. This is the load class method. This is called when the class is loaded into the runtime. It’s a great place to do swizzling so that the method is swizzled before the class is ever used.
  3. Since the load method can be invoked multiple times due to inheritance, you only want the swizzling to occur once.
  4. The SEL variable is basically the string representation of your method. You will be injecting code into the viewDidLoad method.
  5. With the SEL variables, you will get the method implementations of the original and newly defined method.
  6. Fetch the implementations of the two methods that need to be swapped.
  7. method_exchangeImplementations swaps the original method with the new one.
  8. This is the new method you’re swapping in for the original viewDidLoad method.
  9. Here you set a new UIView property you’ll use shortly.
  10. Sharp-eyed readers will note that this looks like it will result in an infinite loop. But since you swapped the method implementations by the time this method runs, this line of code actually calls the original viewDidLoad.

Your code has been injected into each -[UIViewController viewDidLoad] method, but this code won’t compile right now, because UIView doesn’t have a debugVC property.

Add the following code in front of the code you just added above, before the @implementation UIViewController (DebuggingViewInjector) line:

static char kWeakLinkViewControllerKey; // 1
 
@interface UIView ()
@property (nonatomic, unsafe_unretained) UIViewController *debugVC; // 2
@end
 
@implementation UIView (ViewControllerLinker)
 
- (void)setDebugVC:(UIViewController *)debugVC // 3
{
    objc_setAssociatedObject(self, &kWeakLinkViewControllerKey, debugVC, OBJC_ASSOCIATION_ASSIGN); // 4
}
 
- (UIViewController *)debugVC // 5
{
    return objc_getAssociatedObject(self, &kWeakLinkViewControllerKey); // 6
}
 
@end

Taking each numbered comment in turn:

  1. Each associated object requires a unique identifier. This defines a static variable that can be used as the identifier for our associated object.
  2. This declares the debugVC property. It’s unsafe-unretained because you don’t want to hold a strong reference otherwise there would be a retain cycle (view controller owns view which owns the view controller). You’re actually going to override both the getter and setter, so the semantics described by the property are merely a note to the consumer.
  3. This is the setter for the property.
  4. This code sets the associated object. Notice the OBJC_ASSOCIATION_ASSIGN. This defines the `unsafe_unretained` semantics that we described in the property. Sadly there is no way to do a nilling, weak reference with associated objects.
  5. This is the getter for the property.
  6. Fetch the value of the property from the associated object.

Finally, it would be smart to conditionally compile this code when you’re in DEBUG mode.

Surround the entire file — even the #import headers — with the following:

#if DEBUG
    // Original code
#endif

Now it’s time to give this bad-boy category a whirl. Build and run your app; navigate to the part of the app where you previously performed the View Debugging.

Enable the View Debugging using Debug\View Debugging\Capture View Hierarchy. Once the views generate, find the backing view for the side control that holds the gray color. Keep clicking back further and further to hunt down the root UIView that holds all subviews.

When you click on the correct UIView, you’ll see the memory address holding the UIView instance appear in the Utility Area of Xcode. Copy this address to the clipboard, then open up the Xcode console.

Swizzled ViewController Debugging

Type the following commands in lldb:

po [YOURMEMORYADDRESSHERE debugVC]

The console will spit out the resulting viewcontroller that is responsible for that UIView; in this case, it’s TOCViewController.

Open TOCViewController.m in Xcode. Try to find the logic that references a gray color in TOCViewController.m and change it to black. Check your findings against the solution below.

Solution Inside: Solution SelectShow>

Press Ctrl+1 and select User Interface to bring up the file that holds the visual representation of TOCViewController. Use the Search bar in Interface Builder to find TOCViewController, then hunt down the appropriate UIView with the light gray color and change it to black.

Build & run and then navigate to the sidebar again. You should see something like this:

iOS Simulator Screen Shot 19 Sep 2014 10.32.27

Finding Things When You’re Completely Lost

Sometimes you don’t really know where to begin, such as with your third and final ticket:

Ticket #3

Feature Request: toggle the status bar visibility when the screen hides the menu content.

Make sure the status bar animates with the rest of the navbar when animating in and out.

Ticket 3

Depending on your knowledge of UIScrollView‘s delegate, you might have no clue where to start with this feature. All you know is that when you pan up or down on the web content the navbar and toolbar views resize themselves. In order to figure out what’s happening, the best way is to trigger the event when running the app on the device or the simulator and use some intelligent breakpoints to guide your way.

But how do you trigger a breakpoint when you have no clue where to set it? This is where you break out the Where The Heck Am I Breakpoint! :]

With the Wikipedia app running on the simulator, navigate to the same screen illustrated in the ticket. Navigate to the lldb console, pause the debugger, then type the following at the lldb prompt:

rb . -s Wikipedia

This command sets a breakpoint in every single method and function of your app. If you have any long running code (i.e. OpenGL, or background networking) that recurs constantly, this might not be a good option. It works wonders, however, if you’re just lost and need to get a bearing on your location in the code.

Resume the application by pressing the Play button. Start dragging the web view to simulate the action of hiding/displaying the navbar and toolbar. Be sure to use a drag motion; don’t simply just tap on the view or else you’ll break on the wrong event.

You’ll immediately hit a breakpoint from a UIScrollViewDelegate method in WebViewController. Scan the code for anything that looks of interest; there’s nothing here relevant to your interests so continue execution.

After a few more clicks of play, you’ll come across -[WebViewController scrollViewDidScroll]. This UIScrollView delegate method contains code that looks rather interesting due to the aptly named adjustTopAndBottomMenuVisibilityOnScroll method call. Looks like you’re almost there!

Delete the breakpoints in the lldb using the following commands:

br del
About to delete all breakpoints, do you want to do that?: [Y/n] Y
Note: This breakpoint will stop on every single method — and properties are methods. This means you’ll break on every property as well. You can also try rb . -s Wikipedia -o to generate a one-time breakpoint. Alternatively, you can disable the breakpoints in lldb using: br dis and re-enable them using: br en.

Hunt down the correct method using the Cmd-click approach to jump to methods of interest. Check your path to the correct method in the solution below:

Solution Inside: Solution SelectShow>

Navigate to the method provided in the solution above and hunt down the +[UIView animatWithDuration:delay:options:animation:completion] line. It’s here where the changes to the the navbar and toolbar occur.

Insert a call to setStatusBarHidden:withAnimation: into the top of the animation block, so that the beginning of animateTopAndBottomMenuHidden now looks as follows:

-(void)animateTopAndBottomMenuHidden:(BOOL)hidden
{
    // Don't toggle if hidden state isn't different or if it's already toggling.
    if ((self.topMenuHidden == hidden) || self.isAnimatingTopAndBottomMenuHidden) return;
 
    self.isAnimatingTopAndBottomMenuHidden = YES;
 
    // Queue it up so web view doesn't get blanked out.
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
 
        [UIView animateWithDuration:0.12f delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
 
          // *************
          // ADD THIS LINE
          [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:UIStatusBarAnimationSlide];
          // *************
 
  // Remainder of method....

Build and run your app; slide the web content up and down to see how your fix looks.

Fixed ticket #3

Note: Despite the fact that styleguides abound, developers always have unique coding quirks. They’ll typically stick to a specific implementation pattern when dealing with certain scenarios.

For example, a developer might prefer to not hook up any view controllers via segues in Interface Builder and instead rely on Storyboard IDs instantiated through code. It’s the ability to notice these types of patterns and being able to anticipate — without bias — how other developers handle these situations that makes you a great code navigator.

Where to Go From Here?

You can view the changes you made to the Wikipedia repo by checking out the finished-tickets branch in the repository. Use your preferred git check out method or enter the following commands into Terminal:

git stash
git checkout finished-tickets

You have learned how to get a reference point in your codebase while you navigate using symbolic breakpoints. You have learned how to visually associate both UIViews and UIViewControllers — and all their subclasses — using View Debugging.

In addition, you’re leaving here with a nifty lldb command to help you get your bearings when you’re unsure where to start.

Debugging code plays a huge part in navigating code; this tutorial barely covers the basics. If you want to be a better code debugger, check out the videos or Intermediate Debugging tutorial on this site.

In addition, check out Facebook’s Chisel. Chisel is incredibly useful when navigating a new codebase by associating what views are where when navigating code. You may want to check out this tutorial to learn more about Chisel.

Do you have your own tricks and tips for navigating large codebases? Come share it in the forums below!

Navigating a New Codebase: Tips and tricks for getting up to speed quickly is a post from: Ray Wenderlich

The post Navigating a New Codebase: Tips and tricks for getting up to speed quickly appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4384

Trending Articles