Everyone struggles with keeping classes small. Classes get big because they accumulate methods and their methods accumulate lines. Once a method gets too long, sometimes it has to graduate to its own class.

This move from method to object is sometimes awkward, since we’re translating from a verb to a noun. It’s necessary, though. This technique is a great one to hide complexity, and it can reveal abstractions that weren’t apparent before.

Now, the functional programming nerds have it easier here; any extraction of a function is just going to be another function, with more or less the same lines of code. This is one of the costs of OOP, but as we’ll see, this transition from method to object is very straightforward. We can do it in three simple steps.

Overcoming the hurdle of this translation will enable us to ruthlessly refactor, finding an elegance in our code that we couldn’t see before. We’re trying to maximize two values here:

  1. Short methods are better than long methods, and
  2. Methods with fewer parameters are better than methods with more parameters.

As an example, we’ll take push notification token registration code. I’ve used some of the same code as my post Anatomy of a Feature: Push Notifications. Let’s start with our method:

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *trimmedString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    tokenAsString = [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSDictionary *POSTParameters = @{@"device": @{
                                             @"token": tokenAsString,
                                             @"app_id": [[NSBundle mainBundle] bundleIdentifier],
                                             @"app_version": [[NSBundle mainBundle] bundleVersion],
                                             },
                                     };
    [SKRequestManager POST:@"/account/devices" parameters:POSTParameters success:nil failure:nil];
}

This is a messy and complex method, but at least everything has a good name. Let’s try and move it to its own class. We need a name, even a bad one, for the class. Let’s start with SKPushTokenSender.

We’re ready to move to the first step of our refactoring: move the method wholesale into our new class. Do this without changing any code in the method. If we need anything from the outside world, we can pass it in as a parameter to our new method in the new class.

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    [[SKPushTokenSender new] sendToken:deviceToken];
}

@interface SKPushTokenSender

- (void)sendToken:(NSData *)deviceToken;

@end

@implementation SKPushTokenSender

- (void)sendToken:(NSData *)deviceToken {
    NSString *trimmedString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    tokenAsString = [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSDictionary *POSTParameters = @{@"device": @{
                                             @"token": tokenAsString,
                                             @"app_id": [[NSBundle mainBundle] bundleIdentifier],
                                             @"app_version": [[NSBundle mainBundle] bundleVersion],
                                             },
                                     };
    [SKRequestManager POST:@"/account/devices" parameters:POSTParameters success:nil failure:nil];
}

@end

We can now verify that this does the same thing as before with our tests. This class is actually pretty good already! It does one thing, and it does a good job. It’s still internally pretty ugly though, so let’s continue to refactor it. We’re transitioning from method to object, and we’re in an unwieldy middle state.

To get out of this middle state, we perform the second step of this refactoring: method parameters become initializer parameters. Let’s move the deviceToken to an initializer.

@interface SKPushTokenSender

- (instancetype)initWithDeviceToken:(NSData *)deviceToken

@property (nonatomic, readonly) NSData *deviceToken;

- (void)sendToken;

@end

@implementation SKPushTokenSender

- (instancetype)initWithDeviceToken:(NSData *)deviceToken {
    self = [super init];
    if (!self) return nil;
    _deviceToken = deviceToken;
    return self;
}

- (void)sendToken {
    NSString *trimmedString = [[self.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    tokenAsString = [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSDictionary *POSTParameters = @{@"device": @{
                                             @"token": tokenAsString,
                                             @"app_id": [[NSBundle mainBundle] bundleIdentifier],
                                             @"app_version": [[NSBundle mainBundle] bundleVersion],
                                             },
                                     };
    [SKRequestManager POST:@"/account/devices" parameters:POSTParameters success:nil failure:nil];
}

@end

Changing the interface means we should go back and update the app delegate as well. Once we’ve done that, we can take a look at what we have. Our method is still messy, which brings us to the third and final step of this refactoring: local variables become instance methods. Let’s do this first with tokenAsString :

@implementation SKPushTokenSender

- (instancetype)initWithDeviceToken:(NSData *)deviceToken {
    self = [super init];
    if (!self) return nil;
    _deviceToken = deviceToken;
    return self;
}

- (NSString *)tokenAsString {
    NSString *trimmedString = [[self.deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    return [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@""];
}

- (void)sendToken {
    NSDictionary *POSTParameters = @{@"device": @{
                                             @"token": self.tokenAsString,
                                             @"app_id": [[NSBundle mainBundle] bundleIdentifier],
                                             @"app_version": [[NSBundle mainBundle] bundleVersion],
                                             },
                                     };
    [SKRequestManager POST:@"/account/devices" parameters:POSTParameters success:nil failure:nil];
}

@end

The local variable tokenAsString became a method on our object, and was replaced by self.tokenAsString. A language like Ruby makes this really easy. In Ruby, when inside the scope of an object, methods can be called without the self., meaning that the local variables and instance methods can look exactly the same. So as you move stuff out of your gnarly method, you don’t even need to add a self. to make it work. (On the other hand, Ruby makes this really hard. There’s no static type checking, so you have to run your code to see if your changes worked? Like an animal?)

We can do the same thing with POSTParameters, and even the characterSet and trimmedString, if we like.

@implementation SKPushTokenSender

- (instancetype)initWithDeviceToken:(NSData *)deviceToken {
    self = [super init];
    if (!self) return nil;
    _deviceToken = deviceToken;
    return self;
}

- (NSCharacterSet *)characterSet {
    return [NSCharacterSet characterSetWithCharactersInString:@"<>"];
}

- (NSString *)trimmedString {
    return [[self.deviceToken description] stringByTrimmingCharactersInSet:self.characterSet];
}

- (NSString *)tokenAsString {
    return [self.trimmedString stringByReplacingOccurrencesOfString:@" " withString:@""];
}

- (NSDictionary *)POSTParameters {
    return @{@"device": @{
                     @"token": self.tokenAsString,
                     @"app_id": [[NSBundle mainBundle] bundleIdentifier],
                     @"app_version": [[NSBundle mainBundle] bundleVersion],
                     },
             };
}

- (void)sendToken {
    [SKRequestManager POST:@"/account/devices" parameters:self.POSTParameters success:nil failure:nil];
}

@end

We can keep extracting like this until we’re satisfied. The class gets continually flattened and methods get continually shortened until it’s sufficiently elegant. Any method inside here that’s still too long and complex is subject to the same treatment of being hoisted from a method to a bona fide object.

Once we start seeing the final state of the class, we see what its true name should be. Sending is only a thing it does, but its identity is around the token itself. Once we rename the class to SKPushToken, we’re done.