Bloaty Objective C Coding Patterns

This may be ~30 years too late, but as they say the best time to start was 30 years ago, the second best time to start is right now.

I still write a lot of code in Objective C. I know all the pundits tell me that I should be writing in Swift, but I have a lot of Objective C code that I maintain, and I’m more familiar with Objective C (and more importantly all of the tooling underneath it). If you’d like to debate Swift vs ObjC please go somewhere else. For what it’s worth I’ll probably write a similar article about Swift in the near future.

This article is specifically about reducing code generation in Objective C code. I know that people are going to argue that I reduced readability in some of the examples below. This may be true. Readability is pretty arbitrary. At the same time I find that the large apps that we see today are often due to “death by a thousand cuts” and by the time you decide that your app is “fat” it’s difficult to fix. I’m going to present a couple of coding patterns that I have seen that generate larger than necessary code, and you can decide which (if any) you want to add to your own toolbox.

General comments about Objective C

Objective C is “fun” in that essentially no Objective C code is deadstripped at link time. Any classes (and properties and methods there-in) that you compile are going to be linked in to your app whether you use them or not.

Objective C is also very verbose with regard to runtime metadata. Again, every class, property, category, method etc. that you compile will increase your runtime metadata. Sometimes in ways you do not expect.

On to the patterns in no particular order…

Patterns

The linkage “feature” described above is transitive, so if you write an Objective C wrapper around a library written in another language (C/C++ is common) then you are going to link in everything that you wrap whether you use it or not. The urge to expose every feature of a library that you wrap seems to be strong and often ends up with a lot of bloat.

@interface MyUtilities : NSObject
@end
@implementation MyUtilities
+ (void)doSomething;
+ (void)doSomethingElse;
+ (void)heresAMethod;
@end

These are often easy to find by searching your code for classes with the name “.*Util.*”. They are collections of class methods grouped together in a class that never gets instantiated. The bloat here is that:

  • There is a useless class added to the runtime and you get the class and method metadata.
  • Due to the lack of deadstripping these methods are going to exist whether you use them or not (and due to transitivity you get everything they depend upon as well).

Take a hint from CoreGraphics and consider making them C functions instead that will happily deadstrip away if they aren’t being used. The worst offenders tend to be categories on UIColor that just add a pile of new colors, or a “string” class or category that is just a collection of class methods that return localized strings.

In the .h:

@interface MyClass

@end

and then in the .m:

@interface MyClass()
@property NSString *myPrivateProperty;
@property NSString *anotherPrivateProperty;
@end
@implementation
- (instancetype)init {
if ((self = [super init])) {
_myPrivateProperty = @"something";
_anotherPrivateProperty = @"somethingelse";
}
return self;
}

@end

I’ve never really quite grasped the usefulness of private properties, especially when a lot of people still use the ivars directly. Essentially we’ve just created a setter, a getter and the overhead of the property metadata for nothing.

I know that debates around dot notation are over, and dot notation has won. It still drives me nut though when I see code that looks like this:

self.foo.bar.baz = 1;
self.foo.bar.bam = @"happy";
self.foo.bar.bot = [self doSomethingCool];

Dot notation really does hide the fact that under each of the dots is a method call, and debugging this stuff can be maddening when you just want to step into -[doSomethingCool] and have to hit step in 4 times before you get there.

ABar *aBar = self.foo.bar;
aBar.baz = 1;
aBar.bam = @"happy"
aBar.bot = [self doSomethingCool];

generates a lot less code and I personally find it significantly easier to read.

The most common use of this is giant dictionaries that people create with @{} syntax. Taking an example from a piece of code I’m familiar with:

entityLookup = @{
@"\x22": @"quot",
@"\x26": @"amp",
@"\x27": @"apos",
@"\x3c": @"lt",
@"\x3e": @"gt",
@"\u00a0": @"nbsp",
....For another ~250 lines...
};

For each one of these “constant strings”, the compiler will generate something like:

cfstring_quot:
00000001000050b8 dq
0x0000000100010030, 0x00000000000007c8, 0x0000000100003ade, 0x0000000000000004 ; "quot", DATA XREF=+[AppDelegate load]+79
....
aQuot:
0000000100003ade db "quot", 0 ; DATA XREF=cfstring_quot

Moving over to something like:

typedef struct {
const char *key;
const char *entry;
} EntityEntry;
EntityEntry entries[] = {
{"\x22", "quot"},
{"\x26", "amp"},
...
};
NSMutableDictionary *entityLookup =
[NSMutableDictionary dictionary];
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); ++i) {
entityLookup[@(entries[i].key)] = @(entries[i].entry);
}

saved us about 20K of codegen in release for this example. Note that it also used significantly less memory at runtime because almost all of the strings were able to be generated as tagged pointers.

Luckily these cases are reasonably easy to find using a linkmap.

Blocks are very cool and have some great uses. Unfortunately they can be very bloaty. Be aware when you use a block you are often also creating a __block_invoke, __copy_helper_block, __destroy_helper_block and a __block_descriptor per block you use. Coding styles like promises make it very easy to generate a lot of blocks very quickly.

This actually is a very in-depth topic, so I wrote a whole article on it which is less opinionated than this one.

Final words

Note that I am not saying that you should eschew blocks, C++ or boxed types. I am just raising awareness that these “features” come at some cost with regards to binary size, launch times and build times that at some point your users and developers are paying for. I am aware that there is the slippery slope argument that if I am that concerned about binary size that I should be writing in assembly and my answer to that is that you need to be the judge where “readability” or “power” overrules “binary size”.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store