Saturday, December 1, 2012

Objective-C Does Not Belong Outside of Mobile

The power of Objective-C comes from its tight and nearly seamless integration with C. The power of C comes from its focus on efficiency and performance and its quasi-portability. Both of these qualities are essential for mobile development. Mobile devices run on a number of different platforms with different binary types (Android uses ELF, while iOS uses Mach-O) and generally have tight clock and memory constraints. Given these compromises, C is a great choice.

However, C doesn't support object-oriented niceties that were fashionable 10-15 years ago, and it doesn't support functional niceties that are en vogue now. Objective-C does an passable job at the former is trying to get better at support for the latter with the help of ReactiveCocoa and friends.

So, given that Objective-C minimally satisfies the requirements of a modern language, should we extend it to the web? I don't think so. The web doesn't operate under the same CPU and memory constraints, so we can use some of that power to make developers more efficient. In cases where bottlenecks occur, most programming environments still support C development. Objective-C may make sense there. However, most developers aren't building Facebook, and Facebook has even figured out a way to make its code more efficient while harnessing the power of its army of highly skilled PHP developers.

Kevin Lawler makes a case for Objective-C on the server. I'll break down my disagreements with his article below. I'll use Java as a counterexample since it is currently the dominant player and is widely deadpanned as being an awful language. If Objective-C can't beat Java, it doesn't stand a chance against ruby, python, javascript, clojure, scala, or any other server-side cult hits.

Objective-C is not a joy:

In the past few years, quietly, almost invisibly, Apple has transformed its Objective-C language into the best language available. I have been working with Objective-C since the release of the iPhone App Store in 2008. In that time Objective-C has evolved from a clunky, boilerplate-heavy language, into a tight, efficient joy. It is an amazing tool. Anything that I would not write in C I would want to write in Objective-C, were support available.
No. Objective-C is not an efficient use of developer time, nor is it a joy. There is still a ton of boilerplate present. I'm sure there are many other things I hate about Objective-C the language, but these are just a few things I came up with off the top of my head.
  • I must declare both an @interface and @implementation for every class.
  • The compiler doesn't support circular references, you must forward declare classes and protocols that are circularly referenced with @class and @protocol.
  • There is no support for namespaces, leading to names like kBluetoothAMPManagerCreatePhysicalLinkResponseAMPDisconnectedPhysicalLinkRequestReceived
  • There are no private methods. There is no way to be sure that a subclass won't accidentally override one of my internal methods simply by reusing one of my names.
  • No type variables (generics)
  • Can't add nil to Foundation collections
  • No support for inner classes
  • It is a superset of C, so everything you hate about C goes here too.

A paragraph full of fail:

To understand the opportunity facing Objective-C it will help to summarize where Java fails. The original promise of Java was that an application written once would compile and deploy on any architecture. Ignoring that this is false, web shops don't use Java for this reason. Platform inconsistency is an issue for almost no one, and it was never an issue to port correct C/C++ code, universal compatibility being the original promise of C as well. This promise however spurred the creation of the JVM, which was Java's first mistake. The JVM is a nonsense abstraction over the assembly and UNIX system layer. Now code runs through an additional layer, which can be slow, and system interactions must be translated through an otherwise pointless, just-for-this-purpose Java API. In the ideal case, this API replicates the entirety of the UNIX system layer in Java-ese, obscuring any helpful C idioms or UNIX-system knowledge in the process, and creating a pointless set of new knowledge to understand. In the less than ideal case, the API fails to implement system-level functionality and creates a barrier between the application and the machine.
 So much is wrong with this paragraph.
Ignoring that this is false, web shops don't use Java for this reason.
There are certainly edge cases, but this is far from false. Java is very much write-once run everywhere. More importantly, it is compile once, link and run everywhere. I'll grant you that some Java libraries have crazy mavenized builds that can take way too long to run, but that isn't Java's fault. Code that you write in Java will compile the same way on any machine and run basically the same way on any other machine, regardless of endianness, instruction width, or operation system. Web shops depend on this to easily consume libraries from all over the Java ecosystem. I invite Mr. Lawler to look at the amount of platform specific code in the average Apache Jakarta project. I'll bet it is effectively zero.
Platform inconsistency is an issue for almost no one, and it was never an issue to port correct C/C++ code, universal compatibility being the original promise of C as well.
I'm hardly a C badass, but I'm pretty good at Java. I'm not at all confident in my ability to write correct platform independent C code. Thankfully, I only target iOS, and I only build with xcode, just like every other iOS developer out there. Nearly all iOS developers build with Clang, and most are on one of a few key versions. I can't imagine trying to build a reusable library that would compile and link with all the different C/C++ compilers/linkers and run on nearly any system.

Even if we ignore the need to write universally compatible code, I would have to invest great amounts of time in consuming any library that wasn't written specifically for my platform. If I wanted to use Apache commons-codec, but it was written on 32-bit Windows, I'd be very skeptical about consuming it on 64-bit Mac without a thorough review. I have no such concern about Java. I can't believe it is 2012, and I have to make that argument.
This promise however spurred the creation of the JVM, which was Java's first mistake. The JVM is a nonsense abstraction over the assembly and UNIX system layer.
No. The JVM is a portable UNIX system layer that runs everywhere. The JVM brought UNIX to windows, and its portable bytecode has enabled an amazing ecosystem of languages, most of which has the power to interoperate.
Now code runs through an additional layer, which can be slow, and system interactions must be translated through an otherwise pointless, just-for-this-purpose Java API.
I guess Mr. Lawler has never heard of the Hotspot JVM's amazing inlining technologies in its JIT compiler. Also, he links to a stackoverflow answer that basically says Java is not slow at all, though the answer does list some downsides with respect to memory and startup time, both of which aren't issues for the web.

A few more nits to pick:

Garbage collection makes execution (and memory usage) unpredictable. You cannot postpone garbage collection forever. The more critical the execution, the more you want to postpone garbage collection, but the longer you postpone garbage collection, the more of a problem it will eventually be. This is a disaster for applications that need to scale.
There is truth here, but Azul's Zing basically blows it all away.
Oracle now owns Java and is a hostile entity. Java is done. Its future as a product is finished.
Although Oracle decimated the JCP, they have been a terrific steward of Java for features. They actually shipped Java 7, which greatly improved performance and includes many new features (including lots of missing UNIX APIs).
As an aside, tying any new language to the Oracle JVM is destined to be a mistake, for reasons previously mentioned.
The JVM is a great place to run a new language. JRuby applications saw free performance gains of up to 30% just from moving from Java6 to Java7.
In practice, object-oriented programming lets large teams of competent programmers build usable software. The same is rarely said for functional languages. In cases where functional language applications do succeed, they are often treated as prototypes and rewritten.
I can point you to a whole army of people that would disagree with that. I know of 3 different companies just in St. Louis that build very big systems with functional platforms. Of course, this is only anecdotal evidence, but I'm sure functional languages are taking off if a small town like St. Louis has such a good showing. Of course, twitter famously uses functional languages for many things, including processing its massive logs.

The lines that caused me to write this blog post:

5. Xcode is an excellent IDE, with tolerably good git support
This line just killed me. Xcode is the biggest piece of shit modern IDE I've ever used. I outlined my hatred in a presentation I gave to Lamda Lounge. If Mr. Lawler thinks it is excellent, he's clearly never used Eclipse JDT or IntelliJ IDEA, and he's never been amazed at swapping hot code at runtime with JRebel. He must not have ever wanted to use a structural code formatter, either. I'm sure he doesn't care about creating plugins or modifying the tools he uses. I doubt he wants a transparent bug reporting system.
IDE auto-completion works wonderfully.
No, it doesn't. I just barely works. If there are any compilation errors in the class, the autocomplete fails.   It stupidly suggests methods from all over the various C APIs that I've never used and I doubt would ever be applicable to my code. It never prioritizes local variables or methods that I most often use. "NSS" doesn't complete to "NSString", it completes to "NSStream". "NSLo" becomes "NSLoadedClasses".
The library import process is less tedious than in Java.
I think Mr. Lawler is trolling me. Xcode has no auto-import at all. In Eclipse, if I type "Set", "import java.util.Set;" is automatically added at the top. I would love this feature in Xcode. If I want a library in Xcode, I have to use the mouse to navigate through 5 modal operations.

Still Good For Mobile

Again, mobile development operates under a different set of constraints. We need C on mobile, and we need a modern superset of C to build mobile applications. Objective-C is a decent choice. As I outlined above, it has warts, but they are tolerable given the constrained environment of mobile. Thanks for journeying with me on my insane screed.

Friday, July 6, 2012

Categorizing your radars

When you encounter a bug in Apple's iOS code, you need to report it. I was always confused about the Product against which I should report my bug. There are 3 categories that made sense to me:

  • Developer Tools
  • iPad SDK
  • iPhone SDK
Thankfully, former Apple Developer Evangelist @jury cleared it up for me:
Basically, use iPad SDK if you find it in the iPad Simulator/device, use iPhone SDK if you find it in the iPhone Simulator/device. Don't double-report the bug, indicate that it exists in multiple categories in the bug text.

Monday, June 18, 2012

Abstract Methods on Legacy Objects Using Categories

Let's say we have the following classes:


@interface HBAbstractLegacyNode : NSObject

@end

@interface HBChildNode : HBAbstractLegacyNode

@property (nonatomic, strong) NSString *childProperty;

@end

@interface HBParentNode : HBAbstractLegacyNode

@property (nonatomic, strong) NSString *parentProperty;

@end



If we want to add a template method onto HBAbstractLegacyNode, we ordinarily, just alter our classes thusly:


@interface HBAbstractLegacyNode : NSObject

- (void) templateMethod;
- (NSString *) abstractishMethod;

@end

@implementation HBAbstractLegacyNode

- (void) templateMethod {
  NSLog(@"templateMethod: %@", [self abstractishMethod]);
}

- (NSString *) abstractishMethod {
  [NSException raise:@"Override" format:@"%@ should override %@", NSStringFromClass(self), NSStringFromSelector(_cmd)];
  return nil;
}

@end

@implementation HBChildNode 

- (NSString *) abstractishMethod {
  return self.childProperty;
}

@end

@implementation HBParentNode 

- (NSString *) abstractishMethod {
  return self.parentProperty;
}

@end



But what if we're dealing with legacy code or a 3rd party library, and can't or don't want to modify our existing classes? Then, we can utilize categories to add methods, and use protocols to get some compile-time type safety.


@protocol HBOptionalAbstractish 

@optional
- (NSString *) abstractishMethod;

@end

@protocol HBAbstractish 

@required
- (NSString *) abstractishMethod;

@end

@interface HBAbstractLegacyNode(HBTemplate)

- (void) template;

@end

@interface HBParentNode(HBAbstractish)

@end

@interface HBChildNode(HBAbstractish)

@end

@implementation HBParentNode(HBAbstractish)

- (NSString *) abstractishMethod {
    return self.parentProperty;
}

@end

@implementation HBChildNode(HBAbstractish)

- (NSString *) abstractishMethod {
    return self.childProperty;
}

@end

@interface HBAbstractLegacyNode(HBOptionalAbstractish)

@end

@implementation HBAbstractLegacyNode(HBOptionalAbstractish)

@end

@implementation HBAbstractLegacyNode(HBConsumerCodeTemplate)

- (void) template {
    NSLog(@"template: %@", [self abstractishMethod]);
}

@end



We define two parallel protocols, one whose methods are optional, and one whose methods are required. Apple's Category Documentation says:
There’s no limit to the number of categories that you can add to a class, but each category name must be different, and each should declare and define a different set of methods.

Further restrictions exist (I can't find a link to Apple documentation beyond this stackoverflow answer, please help me find a link to real docs):

Although the Objective-C language currently allows you to use a category to override methods the class inherits, or even methods declared in the class interface, you are strongly discouraged from doing so. A category is not a substitute for a subclass. There are several significant shortcomings to using a category to override methods:
  • When a category overrides an inherited method, the method in the category can, as usual, invoke the inherited implementation via a message to super. However, if a category overrides a method that exists in the category's class, there is no way to invoke the original implementation.
  • A category cannot reliably override methods declared in another category of the same class.
    This issue is of particular significance because many of the Cocoa classes are implemented using categories. A framework-defined method you try to override may itself have been implemented in a category, and so which implementation takes precedence is not defined.
  • The very presence of some category methods may cause behavior changes across all frameworks. For example, if you override the windowWillClose: delegate method in a category on NSObject, all window delegates in your program then respond using the category method; the behavior of all your instances of NSWindow may change. Categories you add on a framework class may cause mysterious changes in behavior and lead to crashes.

I interpret the above to mean we should not override methods from categories. Thus, we define the optional protocol who we will implement with a category on our superclass, HBAbstractLegacyNode. Then, we'll implement the required protocol on each of our subclasses.

Unfortunately, we don't have complete compile-time type safety. We must manually ensure that the methods in HBAbstractish and HBOptionalAbstractish have matching signatures, and we must manually ensure that we create category implementations for each new subclass we create. Thus, we don't get complete fidelity with abstract methods you find in Java, but we get close.

Declaration and implementation order matter to ensure that the compiler doesn't think subclasses have inherited an implementation from their superclass. The protocol declarations must come first, but the subclass interface and implementations must come before the superclass'. If the superclass' interface comes before the subclass' implementation, the compiler will think the subclass already has an implementation from the superclass and will not warn us if we haven't provided an implementation. Since the superclass implements the optional protocol and we don't provide any methods in its implementation, the superclass actually provides nothing to its subclasses.

This is a lot of work to get a few helpful hints from the compiler, but when you have an enormous codebase spanning hundreds of files, all these checks (in concert with unit tests), go a long way towards stopping stupid errors.

Thursday, February 9, 2012

iOS 5.0 MKMapView (MapKit MapView) Edge Cases

The following are notes from my adventures implementing complex custom callouts in an MKMapView on iOS 5.0.

  • The selected MKAnnotationView is brought to the front of the group.
  • When the selected MKAnnotationView is deselected, other undefined annotations may appear in front.
  • Calling -[MKMapView selectAnnotation:animated:] on an annotation with a disabled MKAnnotationView will not select the MKAnnotationView. 
    • The MKMapView will not call -[MKMapViewDelegate mapView:didSelectAnnotationView:].
  • If MKAnnotationViews overlap and one is selected, touching the selected one will select an unselected one.  Since custom callouts must be implemented as ordinary MKAnnotationViews, you must disable all MKAnnotationViews behind a callout MKAnnotationView if you want the user to be able to interact with UIControls inside your callout MKAnnotationView
  • Any UIControls in your MKAnnotationView must exist within your MKAnnotationView's frame. Otherwise, when the user attempts to touch them, the map will think the user touched outside the MKAnnotationView and deselect your MKAnnotationView.
  • You may change the frame of your MKAnnotationView after the MKMapView calls -[MKMapViewDelegate mapView:viewForAnnotation:] and -[MKMapViewDelegate mapView:didAddAnnotationViews:].  However, you can't change the -[MKAnnotationView centerOffset].  So, if you change the frame size, it needs to grow equally in all directions.
    • When you don't grow the MKAnnotationView's frame evenly, it will appear to look fine at first, but when you zoom the map, it will snap to its old centerOffset.