traversing an array that might not be there... some questions.

so I have a swift/Cocoa topic that i just want to understand.


I am asking a Core Animation object, CALayer, for an array of it's sublayers, that looks like this:

let myList = layer.sublayers


but CALayer may or may not have sublayers. so this is an optional List. when I work with the list I have to unwrap it:

myList?.count


but if I wanted to traverse the array, shouldn't just unwrapping it work?

for everyItem in myList?{

because it doesn't work. it doesn't remotely work.

XCode throws an error unless I wind up using an exclamation point... which defeats everything I know about conditional unwrapping. So what do I do?

let someItems : [CALayer] = []
if layer.sublayers != nil{
     someItem += layer.sublayers
}
for everyItem in someItems{
     ...
}


because that will make me want to kill myself. Conditional unwrapping should HANDLE this. by doing this:

for everyItem in layer.sublayers?{
...
}


Swift should be able to manage if sublayers is nil. just don't traverse it. But that's not how it works, I have to Force it every single time. or write at least 4 extra lines of code to work around this... and I'm not just against 4 extra lines of code here... this is an example of a design convention all over the mac os x cocoa era frameworks. I'm really concerned about doing this all the time.


meanwhile, it occurred to me that This behavior is really weird. If CALayer's sublayer property was not optional, this would never happen. they could just return the empty array, and problem solved, good day sir.


I get that Obj-C was different, and we have to expect some speed bumps. I'm just having a difficult time believing that this is the correct way of handling this speed bump.

You should file your suggestion to Swift.org if you want this to be considered.


But did you try :

let myList : [CALayer] = layer.sublayers ?? []


Then you can write

for everyItem in myList { }

I will give that a go, thnx.

1. "sublayers" is optional because of its Obj-C history. In Obj-C, if "sublayers", you can still safely refer to "layers.sublayers.count" and get back the value 0. (The language spec guarantees that the return value of sending a message to nil is 0.) So, having nil instead of an empty array is a small optimization that does no harm in Obj-C code. Obviously, it's more of an annoyance in Swift, but we're stuck with it.


2. "?" is not the "unwrapping operator", it's the "optional chaining operator". That's why it works in "myList?.count", but not "for … in myList?". The latter is a syntactic context that doesn't support optional chaining. "!" is the unwrapping operator, which is why you had to use it instead.


3. I recommend that you don't use the pattern you've shown in your code fragment. In Swift, it turns out to be a lousy idea to let optionals "propagate" down the code, under the understanding that you'll deal with them later. (BTW, this pattern works really well in Obj-C, which is why people tend to try it in Swift too.) Instead, you should detect the nil value immediately, and deal with it.


So you can do something like this:


if let myList = layer.sublayers {
     for everyItem in myList {
          …
     }
}


This is (I claim) the proper way to handle this case. The only drawback is that it forces you into an extra level of indentation, which some people don't like. To eliminate that, you sometimes have to get a bit creative. Sometimes, you can use (or you can refactor your code to use) this alternative:


guard let myList = layer.sublayers else { return }
for everyItem in myList {
     …
}


Or, you could even do something like this:


let myList = layer.sublayers ?? []
for everyItem in myList {
     …
}


The key point here is that by dealing with the nil value when it first appears, you eliminate having to randomly sprinkle "?" and "!" into subsequent code, and you generally get to deal with it in a single line, rather than the "4 extra lines of code here" that so justifiably irritates you.

nice spot. I am originally an Obj-c person.


swift 1 was terrible with optionals. errors were profuse, "fix it" suggestions created more errors and were circular in nature. nothing was clear... it was much like memory management in Obj-C, which Swift was designed to fix by Not allowing optionals in the first place. (leading to the : don't throw the baby out with the bathwater decision to include limited support for optionals so Cocoa would work... but don't you use them at home)


I wound up writing the whole thing longhand.

if I had such a chain with multiple items that were optional, I got into problematic code pretty easily.

So to end run around it I settled on a bullet proof solution:

// in the case of: someObject?.anotherObject?.finalObject?
if let sobj = someObject?{
     f let aobj = sobj.anotherObject?{
          if let fobj = aobj.finalObject?{
               ...
          }
     }
}

it ran the amount of boiler plate code through the roof. I'm still irritated about it. this is a stupid solution. And eventually multiplies the amount of code.

Swift is supposed to eliminate this kind of cruft. but it is the only solution that works... apparently in swift 4 as well.

I'm not afraid of indents, there's no reason for

for every in array?{

to have difficulty with the optional. there is nothing vague/inconsistent about that.

For this, you know you can write:


if let sobj = someObject,
   let aobj = sobj.anotherObject,
   let fobj = aobj.finalObject{ …


(with no need for the "?" and no need for multiple scopes/indents), right? Or you can just write:


if let fobj = someObject?.anotherObject?.finalObject {…


to mean exactly the same thing, if you don't need the intermediate unwrapped references (sobj and aobj)? (If I haven't lost my mind, the trailing "?" is redundant in all 4 places you used it.)


>> there's no reason for "for every in array? {" to have difficulty with the optional


Why don't you submit a bug report requesting that the Swift compiler be changed to do the obvious thing with this, namely loop 0 times? It seems like a good thing to request. Or go post over on the swift-users list on swift.org, and the actual Guardians of the Compiler might come back with a good reason why it can't be done.

i don't think those options were available in swift 1.0 and if they were... the learning resources were not deep, and the forums were filled with questions that went unanswered.


i think I will ask swift org to do something. I'm going to sit on it an try to figure out what exactly would work best.

traversing an array that might not be there... some questions.
 
 
Q