It’s not about the unit tests

45 Comments

iOS developers generally don’t unit test. So why then do they as a community seem to enjoy a reputation of quality.

No unit tests. No continuous integration. No TDD.

That pretty much summarizes my last project. It was my first paying iOS gig, and not only did we not apply these cherished practices, we shipped a high quality product.

This really bugged me.

I had always thought unit tests were essential. But this experience with iOS seemed to challenge some deeply cherished assumptions I had developed over the years about writing software, and led me to ask myself some very uncomfortable questions.

Was I regressing in my development practices?
Were unit tests not essential?
Was there something different about iOS development?
And how did the iOS community ship quality product, without unit tests at their core?

These questions kept me up and night, and led to the soul searching contained within this article.

Challenged assumptions

Ten years ago I used to debate people on the merits of unit testing. Not any more. Unit testing has become such a common practice that you’d struggle to find a modern development platform that doesn’t have some sort of automated testing framework.

And why not? The benefits are obvious. You can release, change, and refactor your code with confidence. You save a ton on regression testing. And the feeling of security that comes from knowing you have a suite automated unit tests backing your every change is undeniable.

So imagine my surprise when I entered a community responsible for some of the worlds most loved mobile applications, only to discover they don’t unit test. Even more disturbing, they seem to be getting away with it!

What’s different about iOS?

While I won’t say developing apps on iOS is easier, there are a few things iOS developers have going for them.

1. Smaller screen size.

You can’t fit a lot on a mobile phone screen — there just isn’t a lot of room. The apps also tend to be smaller (many don’t even have a backend). This, when combined with a culture of minimalism, dramatically simplifies things in terms of code and data. There is simply less of both.

2. No legacy.

Mobile apps are so new, iOS developers typically aren’t burdened with 100,000 lines of legacy code the way enterprise developers sometimes are. This let’s them start from scratch unencumbered.

3. One language.

You can build an iOS application knowing only one language: Objective-C. The typical web developer needs no fewer than four or five languages to do anything other than static HTML.

4. A mature platform.

A lot of the heavy lifting is done for you in iOS. If you need to do something with photos, music, or Facebook it’s there. You just plugin.

5. Very visual.

This was probably the biggest difference for me coming from the enterprise. Instead of spending days wading through layers of architecture (mocking and unit testing every step of the way), iOS developer have almost no layers of architecture. They spend almost all their time at the UI layer.

That means the nature of the code they write is often visual. The only way to see if it works is to fire up the simulator and try it out.

As interesting as these differences are however, they still don’t tell the whole story. If they did, every mobile platform would enjoy this higher level of quality. Nope there’s something else going on. Something bigger.

Who cares?

When you do something for a long time, it’s easy to forget what people in your field once did without.

  • Architects used to work with slide rules.
  • Seafarers used to navigate by the stars.
  • Artists used to work solely with their bare hands.
  • Programmers used to program without unit tests (often at a much higher quality they you see today).

And yet in all these fields, practitioners were able to achieve what we today, with all our modern tools, would admit was a high level of quality in their work. What was their secret?

They cared.

These people simply cared more about their craft, and what they were doing, than their contemporaries. They ‘out cared’ the competition. And that is what I see in the iOS community.

They care more about the art.
They care a lot about the exact wording and spacing of text on buttons.
They care a lot of the speed and performance.
And they care a ton about affordances (like remembering where in the scroll list a user was when they put the application in the background).

Apple works very hard to make sure every developer in their ecosystem cares, and they give them the tools so they have no excuse not to.

They walk them painstakingly through how to creating beautiful art for their apps.
They share (and enforce) human interface guidelines for mobile application development.
They curate and block apps that don’t meet certain quality or standards.
And their former CEO was known for calling people up in the middle of the night and tweaking the color of a logo.

It’s in the communities DNA. Here is the letter you get on your first day starting at Apple.

These guys care. They care like artists care. And—I’m just citing my own experience here— the same can’t be said for other platforms I have been a part of.

What does this have to do with unit tests?

Absolutely nothing. And that’s my point.

What leads to quality is something much bigger—more than a collection of software engineering techniques or a collection of practices.

When I entered this community I was under the false impression that if you didn’t write software the way I did, you must be doing it wrong.

Instead I discovered a community that cared more about quality than I did, and that I still had a lot to learn about crafting a quality experience.

That’s what this whole things has taught me. It’s not about the practices. It’s about the spirit, intent, and in which they are applied. Used when applicable. Quickly abandoned when not.

Are unit tests an invaluable tool for writing great software? Heck yes.
Am I going to produce a poor product if I can’t unit test? Hell no.

And that is what this experience taught me. I need to be more than a collection of practices. I will unit test where I can (including iOS) and I will doing whatever else it takes where I can’t.

All I can say is to keep growing sometimes we need to challenge our most cherished assumptions. It doesn’t always feel good, but that’s how we grow, gain experience, and turn knowledge into wisdom.

The second you think you’ve got it all figured out you’ve stopped living.

If you want to see how far down the rabbit hole this whole discussion on quality can go, I suggest picking up a copy of Zen and the Art of Motorcycle Maintenance. It’s not an easy read. But it may change your life.

Oh ya, and keep unit testing.

Update

portugal-flag
Portuguese translation courtesy of Sylvestre Mergulhão.

Learning iOS via the Robots And Pencils Academy

3 Comments

Hi all,

I am very excited to announce the launch of a new service me and my friend Michael have been working on.

It’s called The Robots and Pencils Academy and it’s basically screencasts for people who wanting to learn iOS.

As a recent student of iOS, I know how challenging it can be to get started.

New language.
New tools.
New technology.

It can be overwhelming.

This is the website I wish existed when I got started, and because we love sharing what we’ve learned with others, these screencasts are the result.

Getting started

If you are looking for a gentle introduction to Xcode or iOS development in general checkout:



If you just want to see what Objective-C looks like watch:



and a some others we recently created can be found here.

Training

We will also be offering two day bootcamps through the site. Next one is here in Calgary at the end of the month and another coming up in Winnipeg. You can read more about those courses here:

http://academy.robotsandpencils.com/training

Thank you for listening. Please share this with anyone you think may be interested.

Sincerely,

Jonathan & Michael

Objective-C ternary operator

1 Comment

int velocity;
if (isCar)
   velocity = 100;
else
   velocity = 10;

Is the same as:

int velocity = isCar ? 100 : 10;

Another handy NSLog:

NSLog(@"mondaySwitch: %@", (onOff ? @"ON" :@"OFF"));

How to create a new nib and ViewController

Leave a comment

1. Create a new xib.

2. Dress it up.

3. Create a new ViewController.

Make sure it’s a UIViewController.

4. Associate the view’s file owner with the new ViewController.

Click on the Yellow Cube File’s Owner.
Enter your new ViewController name on Identify Inspector.

5. Set File’s Owner outlet to View.

Click File’s Owner.
Click ‘Connections inspector’.
Drag ‘view’ to the white view square on left hand side.

6. Update AppDelegate.

Do this if you want to use this new xib as your root controller.

Here I changed the default ViewController to UIViewController.
And then set the xib name to the new xib ‘BarView’.

AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIViewController *viewController;
@end

AppDelegate.m

#import "BarViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[BarViewController alloc] initWithNibName:@"BarView" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

Hit Run and Voila!

How to draw circles where ever someone taps your screen – iPhone, iPad, iOS

3 Comments

Here’s a quick example of how to detect where a user taps your screen, and then draw a circle at the point.

Step 1: Detect the touch event.

To detect touch events we are going to need our own UIView. We do all our work here and don’t need to touch the ViewController.

Drag out a generic UIView onto your ViewController.

Create a UIView subclass (i.e. MyView).

Associate the UIView with the subclass.

Then use this code to detect the touch event (you don’t need all this code, but I wanted you to see also how to detect double tap).

MyView.m

//---fired when the user finger(s) touches the screen---
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
    //---get all touches on the screen---
    NSSet *allTouches = [event allTouches];

    //---compare the number of touches on the screen---
    switch ([allTouches count])
    {
        //---single touch---
        case 1: {
            
            //---get info of the touch---
            UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
            CGPoint point = [touch locationInView:self];
            NSLog(@"x=%f", point.x);
            NSLog(@"y=%f", point.y);

            //---compare the touches---
            switch ([touch tapCount])
            {
                //---single tap---
                case 1: {
                    NSLog(@"Single tap");
                } break;
 
                //---double tap---
                case 2: {
                    NSLog(@"Double tap");

                } break;
                    
            }
            
        }  break;
    }
}

If you run this, you should now see output in the console showing you where you’ve tapped.

Step 2: Draw the circle.

To draw a circle we will create a property for the tap point (myPoint) and then trigger a redraw by calling setNeedsDisplay when the property value changes.

MyView.m

@interface MyView()
@property (nonatomic) CGPoint myPoint;
@end

@implementation MyView

@synthesize myPoint = _myPoint;

- (void)setMyPoint:(CGPoint)myPoint
{
    _myPoint = myPoint;
    [self setNeedsDisplay];
}

This is important because we never want to call drawRect directly (there’s stuff that needs to be coordinated and handled with a redraw and it’s better to let Cocoa handle this).

Then we just need to set our property when based on where they tapped.

...
NSLog(@"x=%f", point.x);
NSLog(@"y=%f", point.y);
self.myPoint = point;
...

Now all we need is a drawRect method to draw a circle at our point.

- (void)drawRect:(CGRect)rect
{
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(contextRef, 2.0);
    CGContextSetRGBFillColor(contextRef, 0, 0, 1.0, 1.0);
    CGContextSetRGBStrokeColor(contextRef, 0, 0, 1.0, 1.0);
    CGRect circlePoint = (CGRectMake(touchPos.x, touchPos.y, 10.0, 10.0));

    CGContextFillEllipseInRect(contextRef, circlePoint);
}

Run that – and voila. Lovely blue circles appearing where you user clicks.

Here is the code in it’s entirety (it’s just one class – MyView).

MyView.m

#import "MyView.h"

@interface MyView()
@property (nonatomic) CGPoint myPoint;
@end

@implementation MyView

@synthesize myPoint = _myPoint;

- (void)setMyPoint:(CGPoint)myPoint
{
    _myPoint = myPoint;
    [self setNeedsDisplay];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

//---fired when the user finger(s) touches the screen---
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
    //---get all touches on the screen---
    NSSet *allTouches = [event allTouches];
    
    //---compare the number of touches on the screen---
    switch ([allTouches count])
    {
            //---single touch---
        case 1: {
            
            //---get info of the touch---
            UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
            CGPoint point = [touch locationInView:self];
            NSLog(@"x=%f", point.x);
            NSLog(@"y=%f", point.y);
            
            self.myPoint = point;
            
            //---compare the touches---
            switch ([touch tapCount])
            {
                    //---single tap---
                case 1: {
                    NSLog(@"Single tap");
                } break;
                    
                    //---double tap---
                case 2: {
                    NSLog(@"Double tap");
                    
                } break;
                    
            }
            
        }  break;
    }
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(ctx, 2.0);
    CGContextSetRGBFillColor(ctx, 0, 0, 1.0, 1.0);
    CGContextSetRGBStrokeColor(ctx, 0, 0, 1.0, 1.0);
    CGRect circlePoint = CGRectMake(self.myPoint.x, self.myPoint.y, 50.0, 50.0);
    
    CGContextFillEllipseInRect(ctx, circlePoint);
}

@end

How to pass data from one MVC to another using Objective-C protocols

Leave a comment

Here I’ve got a MVC on the left, wanting to know the value of the selected date from the MVC on the right.

Here’s how you create/define a protocol (DatePicker) for the MVC on the right, and then assign yourself as the delegate for the MVC on the left.

Define the protocol and create a delegate property for it:

DatePickerViewController.h

#import "ViewController.h"

@protocol DatePickerDelegate <NSObject>
- (void)didPickDateWithSelectedDate:(NSDate *)selectedDate;
@end

@interface DatePickerViewController : UIViewController
@property (nonatomic, weak) id <DatePickerDelegate> delegate;
@end

Synthesize your delegate, and then call it when you want the call back to happen.

DatePickerViewController.m

@synthesize delegate = _delegate;

- (IBAction)datePicked:(UIDatePicker *)sender
{
    [self.delegate didPickDateWithSelectedDate:sender.date];
}

With our protocol/delegate created and designed, we are now ready to switch the the other side, and register ourselves for the callback for the MVC on the left.

First we need to say we implement the protocol interface.

ViewController.m

@interface ViewController () <DatePickerDelegate>

Then we need to actually implement it.

- (void)didPickDateWithSelectedDate:(NSDate *)selectedDate
{
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"HH:mm"];
    NSString *formattedDateString = [dateFormatter stringFromDate:selectedDate];
    
    self.dateLabel.text = formattedDateString;
    NSLog(@"didPickAlarmTimeWithSelectedDate: %@", formattedDateString);
}

Then we need to register ourselves to act as the delegate for the DatePickerMVC on the right. A good place to do this is in the prepareForSegue method.

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"dateSegue"]) {
        UIViewController *newController = segue.destinationViewController;
        DatePickerViewController *dateVC = (DatePickerViewController *) newController;
        dateVC.delegate = self;
    }
}

Note for this to work the segue needs an id “dateSegue” which you set by selecting the segue in your story board and assigning it the value.

Run it. And Voila! You got one MVC passing data to another via protocols.

Here is the code in it’s entirety.

DatePickerViewController.h

#import "ViewController.h"

@protocol DatePickerDelegate <NSObject>
- (void)didPickDateWithSelectedDate:(NSDate *)selectedDate;
@end

@interface DatePickerViewController : UIViewController
@property (nonatomic, weak) id <DatePickerDelegate> delegate;
@end

DatePickerViewController.m

#import "DatePickerViewController.h"

@interface DatePickerViewController ()

@end

@implementation DatePickerViewController

@synthesize delegate = _delegate;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (IBAction)datePicked:(UIDatePicker *)sender
{
    [self.delegate didPickDateWithSelectedDate:sender.date];
    NSLog(@"datePicked: %@", sender.date);
}

@end

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@end

ViewController.m

#import "ViewController.h"
#import "DatePickerViewController.h"

@interface ViewController () <DatePickerDelegate>

@end

@implementation ViewController
@synthesize dateLabel;

- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{
    [self setDateLabel:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (void)didPickDateWithSelectedDate:(NSDate *)selectedDate
{
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"HH:mm"];
    NSString *formattedDateString = [dateFormatter stringFromDate:selectedDate];
    
    self.dateLabel.text = formattedDateString;
    NSLog(@"didPickAlarmTimeWithSelectedDate: %@", formattedDateString);
}

-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"dateSegue"]) {
        UIViewController *newController = segue.destinationViewController;
        DatePickerViewController *dateVC = (DatePickerViewController *) newController;
        dateVC.delegate = self;
    }
}

@end

How to create an Objective-C category

1 Comment

What

Categories are objective-c’s way of allowing you to add methods to an existing class without having to touch it’s source.

For example, say we wished had a method on NSString that decorated our string with some pretty output.

- (NSString *) decorate
{
    return [NSString stringWithFormat:@"=== %@ ===", self];;
}

And we wished we could call it directly on NSString like this:

NSLog(@"%@", @"Booya".decorate);

Outputs:

=== Booya ===

Categories allow us to do that. Here’s how.

How

The format of a category is the ClassName you are extending, followed by the name you want to give your category (i.e. “Utils”).

#import "ClassName.h"

@interface ClassName ( CategoryName )
// method declarations
@end

To make a new category in Xcode we basically create a new class. Go:

New File (Command + N).
Select ‘Objective-C category’.

Specify the class you want to add a category onto (i.e. NSString).

Then add your category method to your .h file.

#import <Foundation/Foundation.h>

@interface NSString (Utils)
- (NSString *) decorate;
@end

And your implementation to you .m file.

#import "NSString+Utils.h"

@implementation NSString (Utils)
- (NSString *) decorate
{
    return [NSString stringWithFormat:@"=== %@ ===", self];;
}
@end

Now you can go to the class where you want to use the extension, import your category header and make your method call.

#import "NSString+Utils.h"

- (IBAction)tapMePressed:(id)sender
{
    NSLog(@"%@", @"Booya".decorate);
}

That’s it!

Links that help

Apple documentation on category

Older Entries Newer Entries

Follow

Get every new post delivered to your Inbox.

Join 310 other followers

%d bloggers like this: