Using a shovel as a hammer

November 28, 2018 - Jos van Tol

"Everything is a view" says the Apple Developer Documentation. We're talking about the Interface Builder in Xcode. Their WYSIWYG (do people still use that term?) tool for developing graphical user interface (GUI) files for macOS and iOS apps. The XIB files it creates are actually XML files that hold the information for laying out the user interface. At least that is what I heard. I never really went into it, because, yeah, modern OS's lack a lot of good easy-to-use documentation.

So everything is a view. I believe that every view has it's own render clock. So all NSView or UIView objects handle their own calculations on their own clocks and graphics buffers. That could be smart, I see different windows stacked on top of each other on iPads. It might be faster to handle each window as a different texture to send to the GPU. But what if everything is a view? Is every button, image and even every text label a view? I know they are subclasses of NS/UIViews. So, do all those UI elements have their own rendering context running? That can't be smart!

Maybe it is. Maybe it isn't. I just don't know!

It's the trouble that came with operating systems implementing object-oriented programming in the 90s. We have to work with these huge objects that inherit class after class after class. And most of the time we can't even figure out what's going on because we should privatise or "protect" most or all the data these classes contain. I don't know what my lovely little baby button inherits from it's parent class. Or how much members it has, or how many messages can be sent to it's methods. (How about terminology eh?)

We just don't know how many lines of code the AppKit UI objects each contain... When you carelessly swipe your iPhone screen te the left to get to the next tab, how far in every UI object's class hierarchy do we have to trace back for code to get executed? And I don't even want to know how many lines of code Apple's Foundation type classes like NSNumber, NSString and NSArray contain. I guess that's also just a bit more than just a typedef:

@interface NSArray (NSExtendedArray)

- (NSArray *)arrayByAddingObject:(ObjectType)anObject;
- (NSArray *)arrayByAddingObjectsFromArray:(NSArray *)otherArray;
- (NSString *)componentsJoinedByString:(NSString *)separator;
- (BOOL)containsObject:(ObjectType)anObject;
@property (readonly, copy) NSString *description;
- (NSString *)descriptionWithLocale:(nullable id)locale;
- (NSString *)descriptionWithLocale:(nullable id)locale indent:(NSUInteger)level;
- (nullable ObjectType)firstObjectCommonWithArray:(NSArray *)otherArray;
- (void)getObjects:(ObjectType _Nonnull __unsafe_unretained [_Nonnull])objects range:(NSRange)range NS_SWIFT_UNAVAILABLE("Use 'subarrayWithRange()' instead");
- (NSUInteger)indexOfObject:(ObjectType)anObject;
- (NSUInteger)indexOfObject:(ObjectType)anObject inRange:(NSRange)range;
- (NSUInteger)indexOfObjectIdenticalTo:(ObjectType)anObject;
- (NSUInteger)indexOfObjectIdenticalTo:(ObjectType)anObject inRange:(NSRange)range;
- (BOOL)isEqualToArray:(NSArray *)otherArray;
@property (nullable, nonatomic, readonly) ObjectType firstObject API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@property (nullable, nonatomic, readonly) ObjectType lastObject;
- (NSEnumerator *)objectEnumerator;
- (NSEnumerator *)reverseObjectEnumerator;
@property (readonly, copy) NSData *sortedArrayHint;
- (NSArray *)sortedArrayUsingFunction:(NSInteger (NS_NOESCAPE *)(ObjectType, ObjectType, void * _Nullable))comparator context:(nullable void *)context;
- (NSArray *)sortedArrayUsingFunction:(NSInteger (NS_NOESCAPE *)(ObjectType, ObjectType, void * _Nullable))comparator context:(nullable void *)context hint:(nullable NSData *)hint;
- (NSArray *)sortedArrayUsingSelector:(SEL)comparator;
- (NSArray *)subarrayWithRange:(NSRange)range;

This is the interface/header file for the NSArray class. And it goes on for a while. 90+ methods and a whole lot of extra unnecessary data we can't see because it's encapsulated, hidden. Pretty insane for a data structure that is, in fact, simply a linearly arranged set of data with the same size at a location in memory. I guess this is part of the reason we need crazy fast CPUs for just some simple PDA functionality we already did on our PalmPilots which ran a 50 MHz Motorola processor.

Behold! Our array.

But back to inheritance. Our button inherits NSButton, NSControl, NSView, NSResponder and NSObject. And all these have their own connections and properties, with a lot of functionality built in. In my opinion a basic button is just a struct holding a picture and values for x, y, width and height and maybe a state value. Nothing more. Your input function runs a simple if-statement to decide if the button was pressed.

typedef struct button_type // 28 bytes total
  uint32_t *bitmap;
  uint32_t x, y, w, h;
  } state;
} button;

It is a fundamental problem to try and translate the computer's data to real-life objects. A computer doesn't know what a chair is. It knows what a switch is, nothing else. Trying to implement OOP is to use a shovel as a hammer. It works, but it is overkill and possible not very smart to do. I know, it would be crazy to all go back and write our new version of MS Word in x86 assembly. But use whatever language you use in a sane data-oriented way. We all get better from it, and we won't have to wait around for minutes before Photoshop finally started up.

Watch this great talk by Casey Muratori: "The thirty-million-line problem" for more about this problem from someone that knows what he's talking about.