How Apple Uses Static Class Methods, What's Cool and What's Not

Korey Hinton —> Blog —> iOS —> How Apple Uses Static Class Methods, What's Cool and What's Not

Follow Apple's Best Practices

One of my biggest take-aways from the WWDC 2014 videos, is that Apple is trying to help us follow their best practices. The one problem I am trying to solve in 1 app might very well be similar to a thousand different problems Apple has been addressing for years. Problems like sharing code between extensions and apps can be solved by using MVC and putting relevant Model and Controller code into a framework. Apple has been doing this for a long time. Today let's take a look at how Apple has been using static class methods. They do some pretty cool stuff, with some pretty hacky stuff as well. Let's take a closer look. ../../media/wwdc2014.jpg

What's Cool with Apple's static method implementations

Static Utility Methods

Apple follows an interested pattern, I haven't figured out this pattern's name yet. Basically they put static utility methods that operate on a particular class in that class. This avoids breaking out separate controller or helper classes. Our own code can adopt this pattern as well. Consider many of the helper classes you have that are just have static utility methods. If they are operating on one of your own custom object types, you might consider putting that static method in the custom class instead. The benefit to doing this is that you have code that operates specifically on that class instance right there next to the instance code. Also, this eliminates extra files that are not needed and extra imports. If you are already using that object, then you've already imorted it as well and you'll thereby also get access to those static methods.

Let's make this point a little clearer with an example from Apple. Apple provides an API for animating UIView objects. Since UIView objects and UIView subclasses are primarily the only objects that can be animated anyway Apple put the static animate method in the UIView class instead of breaking out a separate UIViewAnimator class. Let's see this in action.

[UIView animateWithDuration:2 animations:^{
   myView.frame = CGRect(0,0,myView.frame.size.width,myView.frame.size.height);
}];

Notice that animation is a stateless operation. Stateless operations are a key indicator for a potential static method candidate. The only state being changed above is the state of the parameterized input, the views themselves. For this reason a static class method is a good fit. Keep reading, later on in this post I'm going to go into some of Apple's yucky state changing static methods.

Static factory convenience initializers

A convenience, or secondary, initializer is an initializer that makes initialization easier because the convenience initializer will internally call the designated initializer with default values that don't need to be passed in as parameters. The designated initializer, or primary initializer, calls super init in its implementation and also handles all of the possible initial parameterized values.

Below is an example of how to correctly use designated initializer and factory convenience initializer. Now in Xcode 6 and iOS 8 you can use NS_DESIGNATED_INITIALIZER so the compiler can help give you warnings if you misuse the designated initializer ie: if a convenience initializer doesn't call designated initializer you will get a warning.

Person.h

+ (instancetype)newbornNamed:(NSString *)name;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age children:(NSArray *)children NS_DESIGNATED_INITIALIZER;

Person.m

+ (instancetype)newbornNamed:(NSString *)name {
   return [[[self class] alloc] initWithName:name age:0 children:nil];
}
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age children:(NSArray *)children NS_DESIGNATED_INITIALIZER {
   if (self = [super init]) {
      self.age = age;
      self.children = children;
   }
   return self;
}

Calling Code

// Convenience
Person *baby = [Person newbornNamed:@"Amy"];

// Designated
Person *parent = [[Person alloc] initWithName:@"Bob" age:25 children:@[baby]];

Ok so let's compare the far-fetched example above to NSDate. It also has a static convenient initializer for a brand-new date just as we did for a brand-new person. [NSDate date]. Wow, that was simple. That returns an NSDate instance that represents the current date and time.

What's Not Cool with Apple's static method implementations

If you've shunned away from some of Apple's static methods, don't worry you're not the only one. Seeing some of these immediately elicit a feeling of hacky and wrong. These primarily seem to be static methods that internally change state or rely on changed state. Doesn't this break the static paradigm?!

Static Methods with Non-Static Operations, yuck!

UIScreen

Whenever I see this line of code I write it off as hacky and wrong and immediately try to find a better way to accomplish what it is trying do: [[UIScreen mainScreen] bounds]. First of all the views in the view hierarchy have frames and bounds information already. Most of the time I see this code trying to fix rotation issues and View Controllers receive orientation change notifications anyway. Wait, let's think about what this static method is reporting. How does this static method report different bounds based on orientation? Fail!

UIAppearance

If static methods should be stateless operational methods why would they ever have setters? The idea of setting values pertain to class instances, not static class methods. Check out this funky setter made on top of a static call to UINavigationBar.

[[UINavigationBar appearance] setTintColor:[UIColor redColor]];

Take a close look at what the above code is doing. Not only is it breaking all the rules of static methods it is also becoming a global nightmare. While it might sound good to set the tint color of every UINavigationBar in your app, when would you ever need to do that?

In conclusion, let's follow Apple's best practices and not their worst practices when it comes to designing a static API.

Date: 2014-11-13T13:44+0000

Author: Korey Hinton

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0