Category Archives: Cocoa

Intrinsic String Encoding

I was baffled today while investigating a bug in MarsEdit, which a customer reported as only seeming to affect the app when writing in Japanese.

I pasted some Japanese text into the app and was able to reproduce the bug easily. What really confused me, though was that the bug persisted even after I replaced the Japanese text with very straight-forward ASCII-compatible English. I opened a new editor window, copied and pasted the English text in, and the bug disappeared. I copied and pasted back into the problematic editor, and the bug returned. What the heck? Two windows with identical editors, containing identical text, exhibiting varying behavior? I knew this was going to be good.

It turns out there’s a bug in my app where I erroneously ask for a string’s “fastestEncoding” in the process of converting it. The bug occurs when fastestEncoding returns something other than ASCII or UTF8. For example, with a string of Japanese characaters, the fastestEncoding tends to be NSUnicodeStringEncoding.

But why did the bug continue to occur even after I replaced the text with plain English? Well…

The documentation for NSString encourages developer to view it as a kind of encoding-agnostic repository of characters, which can be used to manipulate arbitrary strings, converting a specific encoding only as needed:

An NSString object encodes a Unicode-compliant text string, represented as a sequence of UTF–16 code units. All lengths, character indexes, and ranges are expressed in terms of 16-bit platform-endian values, with index values starting at 0.

This might lead you to believe that no matter how you create an NSString representation of “Hello”, the resulting objects will be identical both in value and in behavior. But it’s not true. Once I had worked with Japanese characters in my NSTextView, the editor’s text storage must have graduated to understanding its content as intrinsically unicode based. Thus when I proceeded to copy the string out of the editor and manipulate it, it behaved differently from a string that was generated in an editor that had never contained non-ASCII characters.

In a nutshell: NSString’s fastestEncoding can return different values for the same string, depending upon how the string was created. An NSString constant created from ASCII-compatible bytes in an Objective-C source file reports NSASCIIStringEncoding (1) for both smallest and fastest encoding:

printf("%ld\n", [@"Hello" fastestEncoding]);	// ASCII (1)

And a Swift string constant coerced to NSString at creation behaves exactly the same way:

let helloAscii =  "Hello" as NSString
helloAscii.fastestEncoding			// ASCII (1)

But here’s the same plain string constant, left as a native Swift String and only bridged to NSString when calling the method:

let helloUnicode = "Hello"
helloUnicode.fastestEncoding		// Unicode (10)

As confusing as I found this at first, I have to concede that the behavior makes sense. The high level documentation describing NSString representing “a sequence of UTF-16 code units” says nothing about the implementation details. It’s a conceptual description of the class, and for the most part all methods operating on an NSString comprising the same characters should be heave the same way. But the documentation for fastestEncoding is actually pretty clear:

“Fastest” applies to retrieval of characters from the string. This encoding may not be space efficient.

As I said earlier, my usage of fastestEncoding was erroneous, so the solution to my bug involves removing the call to the method completely. In fact, I don’t expect most developers will ever have a legitimate needs to call this method. Forthose who do, be very aware that it can and does behave differently, depending on the provenance of your string data!

Selective Selector Mapping

I ran into an interesting challenge while porting some Objective-C code to Swift. The class in question served both as an NSTableView delegate and data source, meaning that it implemented methods both for controlling the table view’s behavior and for supplying its content.

Historically in Cocoa, most delegate relationships were established as informal protocols. If you wanted a particular class to be a table view data source, you simply implemented the required methods. For example, to populate a cell based table view, a data source would implement various methods, including one to indicate how many rows the view should have:

- (NSInteger) numberOfRowsInTableView:(NSTableView *)tableView;

In recent years, Apple has increasingly converted these informal protocols to formal Objective-C protocols. These give the compiler the opportunity to generate errors if a particular class declares compliance, but neglects to implement a required method. At runtime, however, the compliance-checking is still pretty loose. NSTableView consults its data source, checks to see that it implements a required subset of methods, and dynamically dispatches to them if it does.

The dynamic nature of NSTableView hasn’t changed with Swift. An @objc class in Swift that complies with NSTableViewDataSource must still implement the required methods such that Apple’s Objective-C based NSTableView can dynamically look up and dispatch to the required delegate methods. Swift’s method rewriting “magic” even ensures that a delegate method can be written in modern Swift style, yet still appear identically to older Objective-C code:

class MyDataSource: NSObject {
	@objc func numberOfRows(in tableView: NSTableView) -> Int {
		return 0
	}
}

Given an instance of MyDataSource, I can use the Objective-C runtime to confirm that a the legacy “numberOfRowsInTableView:” selector is actually implemented by the class above:

let thisSource = MyDataSource()
thisSource.responds(to: Selector("numberOfRowsInTableView:")) // false

Or can I? False? That’s no good. I’m using the discouraged “Selector” initializer here to ensure I get Swift to look for a very specific Selector, even if it doesn’t appear to be correct to the Swift-adapted side of the runtime.

I was scratching my head, trying to figure out why Objective-C could not see my method. Did I forget an @objc marker? No. Did I forget to make MyDataSource a subclass of NSObject? No. I finally discovered that I could second-guess the default Swift selector mapping to obtain a result that “worked”:

class MyDataSource: NSObject {
	@objc func numberOfRowsInTableView(_ tableView: NSTableView) -> Int {
		return 0
	}
}

let thisSource = MyDataSource()
thisSource.responds(to: Selector("numberOfRowsInTableView:")) // true

Instances of MyDataSource will get the job done for Objective-C calls to “numberOfRowsInTableView:”, but I’ve lost all the pretty formatting that I expected to be able to use in Swift.

There’s something else I’m missing out in my Swift implementation: type checking of MyDataSource’s compliance with the NSTableViewDataSource protocol. Old habits die hard, and I had initially ported my class over with an old-fashioned, informal approach to complying with NSTableViewDataSource: I declared a plain NSObject that happens to implement the informal protocol.

It turns that adding that protocol conformance onto my class declaration not only gains me Swift’s protocol type checking, but changes the way key functions are mapped from Swift to Objective-C:

class MyDataSource: NSObject, NSTableViewDataSource {
	func numberOfRows(in tableView: NSTableView) -> Int {
		return 0
	}
}

let thisSource = MyDataSource()
thisSource.responds(to: Selector("numberOfRowsInTableView:")) // true

Armed with the knowledge that my class intends to comply with NSTableViewDataSource, Swift generates the expected mapping to Objective-C. Notice in this final case, I don’t even have to remember to mark the function as @objc. I guess when Swift is creating the selector mapping for a function, it does so in a few phases, prioritizing more explicit scenarios over more general:

  1. First, it defers to any explicit annotation with the @objc attribute. If I tag my “numberOfRows…” func above with “@objc(numberOfDoodads:)” then the method will be made available to Objective-C code dynamically looking for “numberOfDoodads:”.
  2. If there’s no @objc specialization, it tries to match function implementations with declarations in superclasses or protocols the class complies with. This is what gives us the automatic mapping of Swift’s “numberOfRows(in:)” to Objective-C’s “numberOfRowsInTableView:”.
  3. Finally it resorts to a default mapping based on Swift API Design Guidelines. This is what yielded the default “numberOfRowsIn:” mapping that I first encountered.

This is an example of a Swift growing pain that is particularly likely to affect folks who are adapting older source bases (and older programming mindsets!) to Swift. If you run across a completely vexing failure of Objective-C to acknowledge your Swift class’s protocol compliance, start by making sure that you’ve actually declared the compliance in your class declaration!

Swatch Your Step

Shortly after macOS 10.13 was released, I received an oddly specific bug report from a customer, who observed that the little square “swatches” in the standard Mac color panel no longer had any effect on MarsEdit’s rich text editor.

Screenshot of the macOS standard color panel.

I was able to reproduce the problem in the shipping 3.7.11 version of MarsEdit, which for various reasons is still built using an older version of Xcode, against the 10.6 SDK. The MarsEdit 4 Beta, which is built against the 10.12 SDK, does not exhibit the problem.

It’s not unusual for the behavior of Apple’s frameworks to vary based on the version of SDK an application was built against. The idea is usually to preserve the old behaviors of frameworks, so that any changes do not defy the expectations of a developer who has not been able to build and test their app against a later SDK. Sometimes, the variations in behavior lead to bugs like this one.

Using a totally straightforward demo app, consisting only of an NSTextView and a button to bring up the color panel, I was able to confirm that the bug affects an app that links against the macOS 10.9 SDK, but does not affect an app that links against the 10.10 SDK.

I filed Radar #34757710: “NSColorPanel swatches don’t work on apps linked against 10.9 or earlier.” I don’t know of a workaround yet, other than compiling against a later SDK.

Unordered Directory Contents

Since I updated to macOS 10.13 High Sierra, some of my unit tests broke. Examining the failures more carefully, I discovered that they were making assumptions about the order that Foundation’s FileManager.contentsOfDirectory(atPath:) would return items.

I wrote a quick playground to test the behavior on a 10.12 machine:

import Foundation

let array = try! FileManager.default.contentsOfDirectory(atPath: "/Applications/Utilities")
print("\(array.debugDescription)")

The results come back alphabetically ordered by file name:

[".DS_Store", ".localized", "Activity Monitor.app", "Adobe Flash Player Install Manager.app", "AirPort Utility.app", "Audio MIDI Setup.app", "Bluetooth File Exchange.app", "Boot Camp Assistant.app", "ColorSync Utility.app", "Console.app", "Digital Color Meter.app", "Disk Utility.app", "Grab.app", "Grapher.app", "Keychain Access.app", "Migration Assistant.app", "Script Editor.app", "System Information.app", "Terminal.app", "VoiceOver Utility.app"]

The same playground on 10.13 tells a different story:

["AirPort Utility.app", "VoiceOver Utility.app", "Terminal.app", "Activity Monitor.app", ".DS_Store", "Grapher.app", "Audio MIDI Setup.app", ".localized", "System Information.app", "Keychain Access.app", "Grab.app", "Migration Assistant.app", "Script Editor.app", "ColorSync Utility.app", "Console.app", "Disk Utility.app", "Bluetooth File Exchange.app", "Boot Camp Assistant.app", "Digital Color Meter.app"]

I thought at first this might have been related to the APFS conversion that 10.13 applied to my boot volume, but the same ordering discrepancy occurs for items on my HFS+ volumes as well.

After checking the 10.13 release notes for clues, and finding none, I consulted the documentation. Well, what do you know?

The order of the files in the returned array is undefined.

So, mea culpa. The test code in question probably shouldn’t have ever made assumptions about the ordering of items returned from this method. While it has evidently always been undefined, it appears they are only making good on that promise in 10.13. You have been warned!

Update: It turns out I have some real bugs in my apps, not just in my tests, because of assuming the results of this call will be reasonably sorted. Luckily I use a bottleneck method for obtaining the list of files, and I can impose my own sorting right at the source. If you’re looking to make the same kinds of changes to your app, be sure to heed Peter Maurer’s advice and use “localizedStandardCompare” (available since macOS10.6/iOS4) to obtain Finder-like ordering of the results.

Evergreen Images

Brent Simmons, the original developer of MarsEdit and NetNewsWire, is building a new feed reader app called Evergreen:

Evergreen is an open source, productivity-style feed reader for Macs.

It’s at a very early stage — we use it, but we don’t expect other people to use it yet.

I’ve never been one to shy away from early-stage software, so of course I ran to the GitHub project page, cloned the repository, and built it immediately on my own Mac.

Screenshot of Evergreen about box without a custom icon.

Ahh, the tell-tale sign of a young app: the generic about box. Personally, I like to give apps-in-progress an icon, even if only a placeholder image, as soon as possible. It occurred to me that Apple has done the favor of providing a pretty-darned-suitable image for “Evergreen” in the form of its Emoji glyph of the same name:

🌲

Since I have the source code right here, why don’t I render that tree at a large size in a graphics app, resize it to a million different resolutions, bundle it up and check it in to the Evergreen source base?

Because that’s not nearly as fun as doing it in code. I dove into the Evergreen application delegate class, adding the following function:

private func evergreenImage() -> NSImage? {
	var image: NSImage? = nil
	let imageWidth = 1024
	let imageHeight = 1024
	let imageSize = NSMakeSize(CGFloat(imageWidth), CGFloat(imageHeight))

	if let drawingContext = CGContext(data: nil, width: imageWidth, height: imageHeight, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) {

		let graphicsContext = NSGraphicsContext(cgContext: drawingContext, flipped: false)
		NSGraphicsContext.saveGraphicsState()
		NSGraphicsContext.setCurrent(graphicsContext)

		let targetRect = NSRect(origin: NSZeroPoint, size: imageSize)
		NSString(string: "🌲").draw(in: targetRect, withAttributes: [NSFontAttributeName: NSFont.systemFont(ofSize: 1000)])

		NSGraphicsContext.restoreGraphicsState()

		if let coreImage = drawingContext.makeImage() {
			image = NSImage(cgImage: coreImage, size: imageSize)
		}
	}

	return image
}

In summary this code: creates a CoreGraphics drawing context, renders a huge evergreen Emoji glyph into it, and creates an NSImage out of it.

Then from the “applicationDidFinishLaunching()” function:

if let appIconImage = evergreenImage() {
	appIconImage.setName("NSApplicationIcon")
	NSApplication.shared().applicationIconImage = appIconImage
}

Give the newly created image the canonical name, used by AppKit, for looking up the application icon, and immediately change the application’s icon image to reflect the new value. It worked a treat:

EvergreenEmoji

In programming there is usually a hard way, an easy way, and a fun way. Be sure to take the third option as often as possible.

Resolving Modern Mac Alias Files

When a user selects a file in the Mac Finder and chooses File -> Make Alias, the resulting “copy” is a kind of smart reference to the original. It is similar to a POSIX symbolic link, but whereas a symbolic link references the original by full path, an alias has historically stored additional information about the original so that it stands a chance of being resolved even if the original full path no longer exists.

An alias, for example, can usually withstand being copied from one volume to another. To test this: create a Folder in your home folder called “Test”, and a file within it called “File”. Now, make an alias to “File”. You should have a folder hierarchy that looks like this:

Test
	File
	File alias

Click on the “File Alias” item, then choose “File” -> “Show Original” from the Mac menu bar to see how it resolves to the original.

Now, copy the whole folder to another volume, for example to a thumb drive or other external drive on your Mac. Click on the alias file and “Show Original” again. Instead of resolving to the file on your home volume, it resolves relative to the location on the new volume.

That’s pretty neat.

In the old days, if a developer needed to resolve an alias file on disk, they would use a Carbon function such as FSResolveAliasFile. In recent years, particularly as Application Sandboxing was introduced on the Mac, Apple has shifted away from aliases as a developer-facing concept, towards the use of “Bookmark Data”. As a result, an “alias file” created in the Finder is no longer a traditional alias, but a blob of bookmark data. You can confirm this by examining the hex content of the “File Alias” you created above. From the Terminal:

% xxd File\ alias
00000000: 626f 6f6b 0000 0000 6d61 726b 0000 0000  book....mark....
00000010: 3800 0000 3800 0000 f003 0000 0000 0410  8...8...........
00000020: 0000 0000 0061 0000 768c 979b 7ed8 be41  .....a..v...~..A
00000030: 0000 0000 ff7f 0000 0403 0000 0400 0000  ................
00000040: 0303 0000 0004 0000 0700 0000 0101 0000  ................

It was nice of them to design the format with the tell-tale “book….mark” data right there in the header!

Although you can still use FSResolveAliasFile on these beasts, the function is deprecated and Xcode will warn you about such behavior. The way forward is to use the newer

-[NSURL URLByResolvingBookmarkData:...]

method in Objective-C, or

URL.init(resolvingBookmarkData: ...)

in Swift. Just load the NSData from the alias file’s URL, and pass it to NSURL/URL as appropriate.

There’s a big catch, however, which is that you must take care to pass the alias file’s URL as the “relativeTo:” parameter when resolving the bookmark. Otherwise the bookmark will resolve as expected in typical scenarios, but will fail to resolve in all the scenarios where bookmarks really shine, as for example in the case of moving a bookmark and its target to another volume. So, for example, to resolve an alias file you know exists at “/private/tmp/Test/SomeAlias”:

var targetURL: URL? = nil
do {
	let aliasFileURL = URL(fileURLWithPath: "/private/tmp/Test/SomeAlias")
	let thisBookmarkData = try URL.bookmarkData(withContentsOf: aliasFileURL)
	var ignoredStaleness: Bool = false
	targetURL = try URL(resolvingBookmarkData: thisBookmarkData, options: .withoutUI, relativeTo: aliasFileURL, bookmarkDataIsStale: &ignoredStaleness)
}
catch {
	print("Got error: \(error)")
}

Notice how I ignore the staleness? It’s because I think this notion of staleness is tied more to resolving data with security scope, or in any case a bookmark that you are holding as pure Data, and not one that persists as a file on disk. The safety of ignoring staleness is supported by the fact that, starting in macOS 10.10, there is a new convenience method on NSURL specifically for resolving “alias files”:

var targetURL2: URL? = nil
do {
	let aliasFileURL2 = URL(fileURLWithPath: "/private/tmp/Test/SomeAlias")
	targetURL2 = try URL(resolvingAliasFileAt: aliasFileURL2)
}
catch {
	print("Got error: \(error)")
}

Which takes care of ensuring the “relativeTo:” information is considered, on your behalf. If you don’t need to support Mac OS X 10.9 or earlier, you should use the newest method! Otherwise, I hope the information preceding is of some use.

Test With Swift

I have recently passed a sort of tipping point where I’m indulging more and more in Swift for new code that I add to my projects. There are some instances where I will still create a new class in Objective C, primarily where I anticipate the need for dynamic runtime hijinx that might be more complicated in Swift. In general though, I’m opting for Swift. Finally.

There are many reasons to remain gun-shy about Swift, and I don’t fault anybody too much for choosing to continue forestalling the transition. I’ve spoken with many people who are as tentative as I was or moreso. Some of our collective reasons for waiting may sound familiar to you:

Swift …

  • … is not mature.
  • … requires adding bloated libraries to the app.
  • … presents an impedance mismatch with existing Cocoa design patterns.
  • … is still too risky for production code.

I don’t agree with all of these rationale, especially now that I’ve decided to dive in myself. However, they make a good basis for the argument I’d like you to consider: you should write all new unit tests in Swift.

For many of us who spent years developing a vast collection of Objective-C based classes, it does seem daunting to transition to a new language. But unit tests are different from “regular code” in a number of ways that make them a suitable place to start delving into Swift:

Unit tests …

  • Don’t ship to customers.
  • Can be as bloated as you like.
  • Test the exposed interfaces of classes more than the internal design.
  • Are not technically production code.

I’m sure somebody will argue that tests are so vital to the development process, that they are the last place one should invest in risky technology. I guess what I’m urging you to believe is that Swift is no longer risky technology. It’s not longer coming, it’s here. We will serve ourselves well to adopt it as quickly as practical. And those of us who are daunted by the challenge incorporating it into our existing, Objective-C heavy source bases, have a perfect opportunity in unit testing to get our feet wet while establishing a Swift source base that will live on well into the future. After all, your unit tests should, in theory, outlive any specific implementations of your shipping code.

NSDebugScrolling

I’m working on some heavy NSTextView, NSScrollView, NSClipView type stuff in MarsEdit. This stuff is fraught with peril because of the intricate contract between the three classes to get everything in a text view, including its margins, scrolling offset, scroll bars, etc., all working and looking just right.

When faced with a problem I can’t solve by reading the documentation or Googling, I often find myself digging in at times, scratching my head, to Apple’s internal AppKit methods, to try to determine what I’m doing wrong. Or, just to learn with some certainty whether a specific method really does what I think the documentation says it does. Yeah, I’m weird like this.

I was cruising through -[NSClipView scrollToPoint:] today and I came across an enticing little test (actually in the internal _immediateScrollToPoint: support method):

0x7fff82d1e246 <+246>:  callq  0x7fff82d20562            ; _NSDebugScrolling

0x7fff82d1e24b <+251>:  testb  %al, %al

0x7fff82d1e24d <+253>:  je     0x7fff82d20130            ; <+8160>

0x7fff82d1e253 <+259>:  movq   -0x468(%rbp), %rdi

0x7fff82d1e25a <+266>:  callq  0x7fff8361635e            ; symbol stub for: NSStringFromSelector

0x7fff82d1e25f <+271>:  movq   %rax, %rcx

0x7fff82d1e262 <+274>:  xorl   %ebx, %ebx

0x7fff82d1e264 <+276>:  leaq   -0x118d54fb(%rip), %rdi   ; @“Exiting %@ scrollHoriz == scrollVert == 0”

0x7fff82d1e26b <+283>:  xorl   %eax, %eax

0x7fff82d1e26d <+285>:  movq   %rcx, %rsi

0x7fff82d1e270 <+288>:  callq  0x7fff83616274            ; symbol stub for: NSLog

 

Hey, _NSDebugScrolling? That sounds like something I could use right about now. It looks like AppKit is prepared to spit out some number of logging messages to benefit debugging this stuff, under some circumstances. So how do I get in on the party? Let’s step into _NSDebugScrolling:

AppKit`_NSDebugScrolling:

0x7fff82d20562 <+0>:   pushq  %rbp

0x7fff82d20563 <+1>:   movq   %rsp, %rbp

0x7fff82d20566 <+4>:   pushq  %r14

0x7fff82d20568 <+6>:   pushq  %rbx

0x7fff82d20569 <+7>:   movq   -0x11677e80(%rip), %rax   ; _NSDebugScrolling.cachedValue

0x7fff82d20570 <+14>:  cmpq   $-0x2, %rax

0x7fff82d20574 <+18>:  jne    0x7fff82d20615            ; <+179>

0x7fff82d2057a <+24>:  movq   -0x116a7ad9(%rip), %rdi   ; (void *)0x00007fff751a9b78: NSUserDefaults

0x7fff82d20581 <+31>:  movq   -0x116d5df8(%rip), %rsi   ; “standardUserDefaults”

0x7fff82d20588 <+38>:  movq   -0x1192263f(%rip), %rbx   ; (void *)0x00007fff882ed4c0: objc_msgSend

0x7fff82d2058f <+45>:  callq  *%rbx

0x7fff82d20591 <+47>:  movq   -0x116d5fa0(%rip), %rsi   ; “objectForKey:”

0x7fff82d20598 <+54>:  leaq   -0x118ab0cf(%rip), %rdx   ; @“NSDebugScrolling”

0x7fff82d2059f <+61>:  movq   %rax, %rdi

0x7fff82d205a2 <+64>:  callq  *%rbx

 

Aha! So all i have to do is set NSDebugScrolling to YES in my app’s preferences, and re-launch to get the benefit of this surely amazing mechanism. Open the Scheme Editor for the active scheme, and add the user defaults key to the arguments passed on launch:

Screenshot 3 29 16 3 50 PM

You can see a few other options in there that I sometimes run with. But unlike those, NSDebugScrolling appears to be undocumented. Googling for it yields only one result, where it’s mentioned offhand in a Macworld user forum as something “you could try.”

I re-launched my app, excited to see the plethora of debugging information that would stream across my console, undoubtedly providing the clues to solve whatever vexing little problem led me to stepping through AppKit assembly code in the first place. The results after running and scrolling the content in my app?

Exiting _immediateScrollToPoint: without attempting scroll copy ([self _isPixelAlignedInWindow]=1)

I was a little underwhelmed. To be fair, that might be interesting, if I had any idea what it meant. Given that I’m on a Retina-based Mac, it might indicate that a scrollToPoint: was attempted that would have amounted to a no-op because it was only scrolling, say, one pixel, on a display where scrolling must move by two pixels or more in order to be visible. I’m hoping it’s nothing to worry about.

But what else can I epect to be notified about by this flag? Judging from the assembly language at the top of this post, the way Apple imposes these messages in their code seems to be based on a compile-time macro that expands to always call that internal _NSDebugScrolling method, and then NSLog if it returns true. Based on the assumption that they use the same or similar macro everywhere these debugging logs are injected, I can resort to binary analysis from the Terminal:

cd /System/Library/Frameworks/AppKit.framework
otool -tvV AppKit | grep -C 20 _NSDebugScrolling

This dumps the disssembly of the AppKit framework binary, greps for _NSDebugScrolling, and asks that 20 lines of context before and after every match be provided. This gives me a pretty concise little summary of all the calls to _NSDebugScrolling in AppKit. It’s pretty darned concise. In all there are only 7 calls to _NSDebugScrolling, and given the context, you can see the types of NSLog strings would be printed in each case. None of it seems particularly suitable to the type of debugging I’m doing at the moment. It’s more like plumbing feedback from within the framework that would probably mainly be interesting from an internal implementor’s point of view. Which probably explain why this debugging key is not publicized, and is only available to folks who go sticking their nose in assembly code where it doesn’t belong.

A Eulogy For Objective-C

I missed Aaron Hillegass’s talk at AltConf earlier this year, but was nudged to take a look at the transcript by Caro’s tweet today linking to the talk’s video page on Realm.

Although I’m 100% sure, based on experience, that Aaron’s talk is a pure delight to watch, I also appreciate that I could jump right in and read a transcript of the talk until I get a chance to watch it. Aaron gives a thought-provoking “eulogy” for Objective-C, in which he celebrates its parentage and its life thus far.

When a guy like Aaron Hillegass gives a history of Objective-C, and speaks to its strengths and weaknesses, you should hang on every word. He covers many of the features that distinguish the language, provides a context for when they were added, and gives examples of key technologies that are enabled by them. He is also aware of the tradeoffs some of these features demand:

Loose typing made a lot of things that were difficult in other languages much easier, or possible. It also made bugs that didn’t exist in other languages possible as well. And you embrace that as an Objective-C programmer. You’re like, “This is a language for smart, pedantic, uptight people, I’m going to be very careful and do the right thing when I’m typing in names.

I love his hypothetical quote, and think it condenses the feeling a lot of us long-time Objective-C programmers have about the language. We welcome Swift in many respects, but it’s hard to let go of a language whose idiosyncrasies we’ve grown to love, hate, and ultimately make peace with.

Brent’s Feedback

I love Brent Simmons’s style of responding to my last post, in which I described a cover class for NSURLSession that makes is easier for me to adopt it gradually throughout my source base. Brent:

This is the right way to do it. The callers — including the unit tests — don’t have to know anything about the implementation, since the interface is the same. That’s just good programming.

That’s just good etiquette. When responding to somebody with whom you have a fundamental difference of opinion, lead with a compliment. Brent goes on to say:

It’s also not how I would do it in this specific case.

Brent argues that cover classes have their place when it comes to adapting APIs that are not native to the frameworks or language being developed in. But when there is no impedance mismatch, he says:

I’d rather just use a thing directly, rather than write a class that wraps a built-in class.

All good food for thought. I will add here that in the particular case of my “RSLegacyHTTPRequest” class, I decided to make it a subclass of the working title for my previously mentioned “RSSpiffyRequest,” which is the much less glamorous “RSHTTPSession.” RSHTTPSession is a subclass of NSObject, and happens to own a subsystem of objects that “don’t matter to you” but deeply involve NSURLSession. In fact, interacting with RSHTTPSession will feel a lot like interacting with NSURLSession.

The idea, longer term, is that clients of RSLegacyHTTPRequest will move away from that antiquated interface and towards RSHTTPSession. The argument for subclassing in this case is I like the pattern when it allows me to gradually move good logic upwards, from an antique class to a modern class. Is it awkward that it’s called RSHTTPSession, instead of RSHTTPSessionManager? Maybe. I’ll change it if things get weird.

So I’m doing things my way, adapting an antique class to the future by providing a cover class that translates an old-and-busted interface to NSURLSession, and doing things Brent’s way, by basing the future of my “spiffy” NSURLSession convenience classes on a base class that inherits from NSObject, provides a stable interface, but fully embraces and exposes the NSURLSession philosophy to its clients.