Swift Diary #3: Arrays, Protocols, and Pointer Equality
Let’s say I’m writing a Finder replacement. (Disclaimer: previous statement may or may not be true.)
The common representation of the file system in a GUI app is a tree. A common pattern, then, is to create a tree data structure. The tree is made of Node classes, and each Node has a representedObject which conforms to the NodeRepresentedObject protocol.
A representedObject may be a Folder or a File. All the Node class knows is that its representedObject conforms to NodeRepresentedObject.
Each Node has zero or more children. The children are Node objects also. The children’s representedObjects are a mix of Folders and Files.
* * *
Here’s the trouble I ran into. Given a representedObject and an array of Nodes, I wanted to find the Node with that representedObject.
Things to know: the representedObjects are uniqued (a file system folder has one Folder object; a file system file has one File object). And, because they’re uniqued, they’re also objects rather than structs.
Because this is true, I can use pointer equality on representedObjects.
The Objective-C code might look like this:
- (Node \*)existingChildWithRepresentedObject:(id<NodeRepresentedObject)representedObject {
for (Node \*oneNode in self.children) {
if (oneNode.representedObject == representedObject) {
return oneNode;
}
}
return nil;
}
Pretty simple.
* * *
My first go at doing this in Swift looked like this:
func existingChildWithRepresentedObject(representedObjectToFind: NodeRepresentedObject) -> Node? {
for oneChild in children {
if oneChild.representedObject === representedObjectToFind {
return oneChild
}
}
return nil
}
This didn’t compile: I get the error error: binary operator '===' cannot be applied to two NodeRepresentedObject operands
.
“But,” I thought, ”shouldn’t pointer equality always work?”
Well, no. Not if representedObject is a struct.
So it occurred to me that I have to tell Swift that these are both objects, and it works:
if (oneChild.representedObject as! AnyObject) === (representedObjectToFind as! AnyObject)
* * *
But I don’t like using as!
— as Rob Rhyne writes, “But every time you type !
, someone at Apple deletes a Radar.”
So I figured there must be a better way. And there is.
-
Keep the original line as it was:
if oneChild.representedObject === representedObjectToFind
. -
Make NodeRepresentedObject conform to the AnyObject protocol, which lets the compiler know that representedObject is an object, so that pointer equality testing will work:
protocol NodeRepresentedObject: AnyObject {
It works. See the gist.
* * *
Here’s the part I don’t totally like: to make this work, it required bringing in Objective-C. AnyObject is an @objc protocol.
So here’s my question: is there a way to do this without bringing in Objective-C?
(I feel like there are two Swifts. Swift + Objective-C is like writing Objective-C with a new syntax. Pure Swift is another thing. I’m pragmatic, and I’ll use the former as needed — but I want to learn the pure Swift solutions.)
* * *
Update 10:15 am: the answer came quite quickly from Matt Rubin on Twitter and others:
try: protocol NodeRepresentedObject: class { … }
Done! It works.
* * *
Update 10:40 am: and there’s also this, which I didn’t know, from Joe Groff on Twitter:
AnyObject is marked ‘@objc’ for obscure implementation reasons, but it is a native Swift feature.