While working on a Swift protocol-oriented and generic linked list, I got to thinking about Apple’s “improvements” to version 4.2 of their flagship language. Since a linked list is a list, I thought, “Why not add a subscript to my linked list to facilitate finding specific nodes in my list?” I did that in Swift 4.1 and got what most developers would’ve expected, e.g., used linkedList["node4"]
to get the node in the list associated with the keyword “node4.” With Swift 4.2, I can use the controversial new @dynamicMemberLookup
language attribute and implement dot/member notation, like linkedList.node4
to get that same node in the list associated with “node4.” Big improvement, huh? Well, maybe. We’ll talk about how this new and improved subscript is more than just about syntactic sugar, but that the “driving motivation for this feature is to improve interoperability with inherently dynamic languages like Python, Javascript, Ruby and others.” Note that all code shown in this tutorial was written in two Xcode 10 beta playgrounds.
Since you may be interested, first let me point you to what I’ve already discussed in regards to Swift 4.2’s new features, then we’ll get back to subscripts.
We’re in the middle of Apple’s annual product upgrade cycle and this article is the third in a series of tutorials meant to shed light on highly-focused and singular aspects of Swift 4.2. Instead of trying to cover all of the 4.2 features/improvements in one very long article, I’m talking about each aspect of the new 4.2 version. So far, I’ve covered these topics:
- Swift 4.2 improvements: #warning and #error compiler directives
- Swift 4.2 improvements to Collection and Sequence protocols with method allSatisfy
The old subscript
notation
Let’s first look at the Swift 4.1 version of a subscript in my protocol-oriented and generic linked list. In this section, we’ll just concentrate on the custom subscript
method I added to an extension for my LinkedListProtocol
protocol. (If you want to examine the entire linked list, click here to go to the last section in this post). The tag
member property in each Node
instance in my LinkedList
is of type String
and is a convenience property enabling me to easily identify and find nodes in my doubly linked list. Here’s the bracketed subscript
method — and note that I’ve highlighted lines 12 and 19 to draw your attention to my subscript
declaration and definition, respectively:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
... protocol LinkedListProtocol { associatedtype AnyType var head: Node<AnyType>? { get set } var tail: Node<AnyType>? { get set } mutating func append(_ newNode: Node<AnyType>) mutating func delete(_ node: Node<AnyType>) func showAll() func showAllInReverse() subscript(tag: String) -> Node<AnyType>? { get } func prepareForDealloc() } extension LinkedListProtocol { ... subscript(tag: String) -> Node<AnyType>? { if head == nil { return nil } else { var nextNode = head while nextNode != nil { if tag == nextNode?.tag { return nextNode } else { nextNode = nextNode?.next } } return nil } } // end subscript } // end extension LinkedListProtocol ... |
In order for you to see how this subscript is working with my linked list, let’s look at some code in my Xcode 10 beta playground. I initialize an instance of my generic linked list for type UIView
, create/initialize nodes, each with unique UIView
instance and meaningful name, configure one of the UIView
instances for display, add the nodes to my linked list, print all the nodes in my list to console, show an example of subscript usage, and then display the payload
of one of the list’s nodes (a UIView
), with tag
“View 4,” on screen — and note that I’ve highlighted line 33 to draw your attention to use of a custom subscript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
... // declare a linked list of type UIView // and initialize it var linkedList = LinkedList<UIView>() // create some nodes to be added to my list, // initializing each with a UIVIew and tag/name let node1 = Node(with: UIView(), named: "View 1") let node2 = Node(with: UIView(), named: "View 2") let node3 = Node(with: UIView(), named: "View 3") // configure one of the UIView's in a node to // be displayed on screen let frame = CGRect(x: 100, y: 200, width: 200, height: 20) let view = UIView(frame: frame) view.backgroundColor = UIColor.blue let node4 = Node(with: view, named: "View 4") let node5 = Node(with: UIView(), named: "View 5") // add the nodes to my linked list linkedList.append(node1) linkedList.append(node2) linkedList.append(node3) linkedList.append(node4) linkedList.append(node5) // print the list to console linkedList.showAll() // use the subscript to get the view // I configured for display out of my list let findView4 = linkedList["View 4"] // Node<__C.UIView> _ = findView4?.tag // "View 4" _ = findView4?.payload?.backgroundColor // r 0.0 g 0.0 b 1.0 a 1.0 class MyViewController : UIViewController { override func loadView() { let view = UIView() view.backgroundColor = .white // display my "View 4" UIView on screen view.addSubview((findView4?.payload)!) self.view = view } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController() |
Here’s what happens when I inspect the node retrieved using my custom subscript (linkedList["View 4"]
) in my Xcode 10 beta playground:
I love how well Xcode allows me to inspect the run-time contents of one of my Node<AnyType>
(at this point, Node<UIView>
) instances. I can even see, expand, and inspect the node’s next
and previous
pointers, er, ah, references.
Here’s the output to console from the linkedList.showAll()
call you saw above:
1 2 3 4 5 |
View 1 View 2 View 3 View 4 View 5 |
Here’s the view (with tag “View 4”) that I configured for drawing and rendered to screen:
The new subscript
notation
The Swift evolution proposal, SE-0195, for a new subscript protocol, was adopted and implemented in Swift 4.2. The proposal states:
This proposal introduces a new @dynamicMemberLookup
attribute. Types that use it provide “dot” syntax for arbitrary names which are resolved at runtime — in a completely type safe way. This provides syntactic sugar…
If I want to use the new dotted subscript in my code — e.g., write linkedList.View4
instead of writing linkedList["View 4"]
as I did above — I just needed to take a few steps. I applied the new @dynamicMemberLookup
attribute to my generic linked list’s
LinkedListProtocol
. Then I applied the dynamicMember
modifier to my protocol definition’s subscript
method argument. I also applied the dynamicMember
modifier to my protocol extension’s subscript
method argument.
Now let’s look at the Swift 4.2 version of a subscript in my protocol-oriented and generic linked list. In this section, we’ll just concentrate on the custom subscript
method I added to an extension for my LinkedListProtocol
protocol. (If you want to examine the entire linked list, click here to go to the last section in this post). The tag
member property in each Node
instance in my LinkedList
is of type String
and is a convenience property enabling me to easily identify and find nodes in my doubly linked list. Here’s some code including the new subscript
method — and note that I’ve highlighted lines 2 , 13, and 20 to draw your attention to the new Swift @dynamicMemberLookup
attribute and the dynamicMember
modifier in my subscript
method’s argument:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
... @dynamicMemberLookup protocol LinkedListProtocol { associatedtype AnyType var head: Node<AnyType>? { get set } var tail: Node<AnyType>? { get set } mutating func append(_ newNode: Node<AnyType>) mutating func delete(_ node: Node<AnyType>) func showAll() func showAllInReverse() subscript(dynamicMember tag: String) -> Node<AnyType>? { get } func prepareForDealloc() } extension LinkedListProtocol { ... subscript(dynamicMember tag: String) -> Node<AnyType>? { if head == nil { return nil } else { var nextNode = head while nextNode != nil { // Allow use of spaces in TAG names. Remember // that for SUBSCRIPTS, you have to use ".View1" in code // because ".View 1" will generate a compiler error. let tagWithNoSpaces = nextNode?.tag?.replacingOccurrences(of: " ", with: "") if tag == tagWithNoSpaces { return nextNode } else { nextNode = nextNode?.next } } return nil } } // end subscript ... |
In order for you to see how the new Swift 4.2 subscript protocol is working with my linked list, let’s look at some code in my Xcode 10 beta playground. I initialize an instance of my generic linked list for type UIView
, create/initialize nodes, each with a unique UIView
instance and meaningful name, configure one of the UIView
instances for display, add the nodes to my linked list, print all the nodes in my list to console, make use of my custom subscript, experiment with the new subscript protocol, and then display one of the list’s nodes, with tag
“View 4,” on screen — and note that I’ve highlighted lines 33 and 38 – 43 to draw your attention to use of a custom subscript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
... // declare a linked list of type UIView // and initialize it var linkedList = LinkedList<UIView>() // create some nodes to be added to my list, // initializing each with a UIVIew and tag/name let node1 = Node(with: UIView(), named: "View 1") let node2 = Node(with: UIView(), named: "View 2") let node3 = Node(with: UIView(), named: "View 3") // configure one of the UIView's in a node to // be displayed on screen let frame = CGRect(x: 100, y: 200, width: 200, height: 20) let view = UIView(frame: frame) view.backgroundColor = UIColor.red let node4 = Node(with: view, named: "View 4") let node5 = Node(with: UIView(), named: "View 5") // add the nodes to my linked list linkedList.append(node1) linkedList.append(node2) linkedList.append(node3) linkedList.append(node4) linkedList.append(node5) // print the list to console linkedList.showAll() // use the "subscript" to get the view // I configured for display out of my list let findView4 = linkedList.View4 // Node<__C.UIView> _ = findView4?.tag // "View 4" _ = findView4?.payload?.backgroundColor // r 1.0 g 0.0 b 0.0 a 1.0 // experiment with new subscript _ = linkedList.View1?.tag // "View 1" _ = linkedList.View2?.payload // UIView _ = linkedList.View3?.tag // "View 3" _ = linkedList.View4?.tag // "View 4" _ = linkedList.View5?.tag // "View 5" _ = linkedList.View6 // nil and no crash class MyViewController : UIViewController { override func loadView() { let view = UIView() view.backgroundColor = .white // display my "View 4" UIView on screen view.addSubview((findView4?.payload)!) self.view = view } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController() |
Let’s inspect the node returned by using the new dotted subscript notation:
Here’s the output to console from the linkedList.showAll()
call you saw above:
1 2 3 4 5 |
View 1 View 2 View 3 View 4 View 5 |
Here’s the view (with tag “View 4” and subscript .View4
) that I configured for drawing and rendered to screen:
So what’s really new?
While the new dotted subscript syntax does provide a bit of syntactic sugar, the “driving motivation for this feature is to improve interoperability with inherently dynamic languages like Python, Javascript, Ruby and others.” Let’s concentrate on syntactic sugar in this section and leave talk about “interoperability with inherently dynamic languages” to the next section.
Unless you’re planning on interoperating with “inherently dynamic languages,” I would advise you to make use of subscripts sparingly and judiciously. In the case of my generic linked list, I determined that having the ability to find a node with a subscript to be helpful. Since my subscript
method returns an optional, that reminds me to check it for nil
before assuming that a Node
matching the subscript (tag
in this case) was found. I can get away with statements like this one at runtime:
1 2 3 4 5 6 7 8 |
if let nada = linkedList.View6 { print("View6 is a Node: \(nada)") } else { print("View6 is NOT a Node") } |
The previous code snippet prints the following to console:
1 |
View6 is NOT a Node |
With Swift 4.2 and using my generic linked list as an experimental use case, we can see that an “old-fashioned” subscript like linkedList["View 4"]
can now be written as linkedList.View4
. Some may bring up the point that “View 4” is stringly-typed and thus prone to spelling errors. Remember too that the Swift evolution proposal, SE-0195 claims that the “dot ‘syntax'” for subscripts is “completely type safe” (their emphasis added). But is it really (type) safe?
In my @dynamicMemberLookup
-attributed LinkedListProtocol
extension, the subscript
method is type safe in the sense that I can specify the input parameter(s) and their type(s) and I can specify the return type. A developer using my subscript
method can glean the input parameters, their types, and the return type from reading the method’s signature — and documentation helps (click here for my Swift how-to on professional code commentary):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... /** Dotted notation subscript to find a node in the list. For example, if you set the tag for a Node to "View1", then you could access that Node with linkedList.View1. Usage: let nodeToFind = linkedList.NodeTagValue - parameters: - tag: Value describing the node - returns: Optional Node (generic) */ subscript(dynamicMember tag: String) -> Node<AnyType>? { ... |
So technically it’s “safe…” well, almost. These Swift subscripts, whether bracketed (linkedList["View 4"]
) or dotted (linkedList.View4
), are not checked at compile time. They are “arbitrary names which are resolved at runtime.”
Let me edit the code shown above like so:
1 2 3 |
... let findView4 = linkedList.View6 // nil ... |
This compiles fine, but since there’s no Node
with a tag
of “View 6,” my code crashes at runtime because I’m trying to render a UIView
which is nil
. It crashes on lines 6,11 in the following code snippet:
1 2 3 4 5 6 7 8 9 10 11 |
... class MyViewController : UIViewController { override func loadView() { let view = UIView() view.backgroundColor = .white // display my "View 4" UIView on screen view.addSubview((findView4?.payload)!) self.view = view } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController() |
Here’s what the crash looks like in the playground:
So remember that if you use bracketed (linkedList["View 4"]
) or dotted (linkedList.View4
), the compiler can’t prevent you from typos (spelling errors). Your typos will cause your app to crash at runtime. Auto-complete doesn’t provide direct assistance with dotted subscripts (but don’t give up on it yet…):
Auto-complete does know where to pick up after you fill in your subscript, and note that I added context sensitive help for my Node<AnyType>
property named payload
:
As you can see, the dotted subscript does perform as advertised and produce the node I wanted based on the subscript I typed:
Interoperating with dynamic languages
In the “Introduction” to the now-implemented Swift evolution proposal, SE-0195, for a new dotted subscript protocol, “syntactic sugar” is mentioned in the third sentence. My main job in this tutorial has been in showing you how an “old-fashioned” subscript like linkedList["View 4"]
can now be written as linkedList.View4
. Keep in my that you don’t have to use the new @dynamicMemberLookup
attribute and you can still write and use bracketed subscript notation. And remember that:
You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts are not limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs.
For many of you just interested in using Swift for writing iOS and/or macOS apps, you can probably just read the next sentence, make a mental bookmark of its contents, and keep an eye out for the latest news on Swift (just in case something big happens). The main purpose for the new dotted subscript protocol is for Swift to stay relevant and see its developer base grow. In order to do so, Swift is going to have to keep up with all the other in-demand dynamic/scripting languages.
If you read on a bit farther into the proposal’s “Introduction” section, you find the real story:
The driving motivation for this feature is to improve interoperability with inherently dynamic languages like Python, Javascript, Ruby and others. That said, this feature is designed such that it can be applied to other inherently dynamic domains in a modular way. …
If you move on to the next section, “Motivation and Context,” you find that the first sentence states:
Swift is well known for being exceptional at interworking with existing C and Objective-C APIs, but its support for calling APIs written in scripting languages like Python, Perl, and Ruby is quite lacking.
There are pages of explanations, sample code, alternative approaches — even a section on “Reducing Potential Abuse” — that go on and on about dynamic languages. It’s beyond the scope of this tutorial to discuss all the details behind this proposal, but this information is there if you want it.
My protocol-oriented, generic linked list code
This is an experimental first draft of a protocol-oriented generic linked list. It’s working great so far, but there are some things I’d like to polish up. You’ll soon be seeing a tutorial on how I designed and built a protocol-oriented generic linked list, the hurdles I had to overcome, what I learned in the process, etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
import UIKit import PlaygroundSupport protocol NodeProtocol { associatedtype AnyType var tag: String? { get } var payload: AnyType? { get set } var next: Self? { get set } var previous: Self? { get set } init(with payload: AnyType, named tag: String) } final class Node<AnyType>: NodeProtocol { var tag: String? var payload: AnyType? var next: Node<AnyType>? var previous: Node<AnyType>? init(with payload: AnyType, named tag: String) { self.payload = payload self.tag = tag } deinit { print("Deallocating node tagged: \(tag!)") } } @dynamicMemberLookup protocol LinkedListProtocol { associatedtype AnyType var head: Node<AnyType>? { get set } var tail: Node<AnyType>? { get set } mutating func append(_ newNode: Node<AnyType>) mutating func delete(_ node: Node<AnyType>) func showAll() func showAllInReverse() subscript(dynamicMember tag: String) -> Node<AnyType>? { get } func prepareForDealloc() } extension LinkedListProtocol { mutating func delete(_ node: Node<AnyType>) { // Remove the head? if node.previous == nil { if let nodeAfterDeleted = node.next { print("Delete head \(node.tag) with followers") nodeAfterDeleted.previous = nil head?.next = nil head = nil head = nodeAfterDeleted node.next = nil node.previous = nil } else { print("Delete head \(node.tag)") head = nil tail = nil node.next = nil node.previous = nil } } // Remove the tail? else if node.next == nil { if let deletedPreviousNode = node.previous { print("Delete tail \(node.tag) with predecessors") deletedPreviousNode.next = nil tail?.previous = nil tail = nil node.next = nil tail = deletedPreviousNode node.next = nil node.previous = nil } else { print("Delete tail \(node.tag)") head = nil tail = nil node.next = nil node.previous = nil } } // Remove node BETWEEN head and tail? else { if let deletedPreviousNode = node.previous, let deletedNextNode = node.next { node.next = nil node.previous = nil print("Delete internal node: \((node.tag))") deletedPreviousNode.next = deletedNextNode deletedNextNode.previous = deletedPreviousNode } } } // end func delete mutating func append(_ newNode: Node<AnyType>) { if let tailNode = tail { print("Appending \(newNode.tag!) to tail") tailNode.next = newNode newNode.previous = tailNode } else { print("Appending \(newNode.tag!) to head") head = newNode } tail = newNode } // end func append func showAll() { print("\nPrinting list:") var nextNode: Node<AnyType>? if let head = head { nextNode = head repeat { if let tag = nextNode?.tag { print("\(tag)") } nextNode = nextNode?.next } while (nextNode != nil) } print("-------------------------\n") } // end func showAll() func showAllInReverse() { print("\nPrinting list in reverse:") var previousNode: Node<AnyType>? if let tail = tail { previousNode = tail repeat { if let tag = previousNode?.tag { print("\(tag)") } previousNode = previousNode?.previous } while (previousNode != nil) } print("-------------------------\n") } // end func showAllInReverse() subscript(dynamicMember tag: String) -> Node<AnyType>? { print("dynamicMember tag: String") if head == nil { return nil } else { var nextNode = head while nextNode != nil { // Allow use of spaces in TAG names. Remember // that for SUBSCRIPTS, you have to use ".View1" in code // because ".View 1" will generate a compiler error. let tagWithNoSpaces = nextNode?.tag?.replacingOccurrences(of: " ", with: "") if tag == tagWithNoSpaces { return nextNode } else { nextNode = nextNode?.next } } return nil } } // end subscript // Unlink all nodes so there are no // strong references to prevent // deallocation. func prepareForDealloc() { var previousNode: Node<AnyType>? if var tail = tail { previousNode = tail repeat { if let tag = previousNode?.tag { //print("\(tag)") } tail = previousNode! previousNode = previousNode?.previous tail.previous = nil tail.next = nil } while (previousNode != nil) // nil is head } } // end func showAllInReverse( } // end extension LinkedListProtocol final class LinkedList<AnyType> : LinkedListProtocol { var head: Node<AnyType>? var tail: Node<AnyType>? init() { head = nil tail = head } deinit { prepareForDealloc() head = nil tail = nil print("Deallocating linked list") } } |
Enjoy!