So if browseResult.enpoint
is equal the value returned by the method
call to NWEndpoint.service(name: name, type: _, domain: _, interface: _)
the variable title
is set equal to Join (name)
.
Yes and no. That’s the effect you’re seeing here but it’s best not to think of it in terms of equality. Rather, think of it in terms of pattern matching. The case
clause in the guard
statement matches the browseResult.endpoint
value against the let NWEndpoint.service(name: name, type: _, domain: _, interface: _)
pattern, and then binds name
to the endpoint’s associated value.
Personally I would’ve used a slightly different syntax for this:
if let browseResult = browseResult,
case NWEndpoint.service(name: let name, type: _, domain: _, interface: _) = browseResult.endpoint {
title = "Join \(name)"
}
By moving the let
closer to the binding site, it makes it clearer that you’re binding that specific value [1].
I am accustomed to "==" being used for a Boolean equality test, and
"=" being used to change value of something. Why does Swift break with
that tradition from C, and C++, here?
Because this isn’t about equality, it’s about pattern matching.
As Claude31 explained, you can think of:
if case <pattern> = <value> {
<match code>
} else {
<no match code>
}
as being a transformation of:
switch <value> {
case <pattern>:
<match code>
default:
<no match code>
}
Swift switch
statements don’t just use equality, they use pattern matching, which is way more powerful (and way more complicated). Hence Claude31 linking to my cheat sheet.
Now you could argument that this shouldn’t use the =
sigil, and I think that’s fair. However, it shouldn’t use the ==
sigil because that’s also confusing. Something like ~=
would’ve been nice, because that’s reminiscent of the standard pattern matching operator. The choice to use =
was made many years ago, perhaps even before the advent of Swift Evolution. You could bounce over there and try to convince folks to change it, but I don’t fancy your chances.
Another point of confusion for me are the underlined characters used
as parameter values in the method call. What does that mean?
Again, this is much clearer once you start thinking in terms of pattern matching. In the pattern matching syntax, _
is treated as a wildcard.
And what of
if let browseResult = browseResult,
? Does this test whether browseResult
has a nil
value?
Again, yes and no. In an if
statement, a let
clause tries to unwrap the optional and:
-
If it’s nil
, the clause evaluates to false
-
If it’s not nil
, the clause evaluates to true and it binds the new name to the unwrapped value
It’s less confusing if you use different names on both sides. For example:
let browseResultQ: NWBrowser.Result? = … something …
if let browseResult = browseResultQ {
… use the non-optional `browseResult` …
}
is roughly equivalent to:
let browseResultQ: NWBrowser.Result? = … something …
if browseResultQ != nil {
let browseResult = browseResultQ!
… use the non-optional `browseResult` …
}
If you’re coming from a C background, understanding the concept of optionals is critical to understanding Swift.
In C, every pointer has a ‘magic’ value (0, nil
, NULL
, nullptr
, and so on) that’s invalid. There’s no way, in the type system, to say “I only work on non-nil pointers.” In Swift that’s not the case. An object reference, like NWConnection
, is different from an optional object reference, written as NWConnection?
.
Swift optionals are made up of two separate things [2]:
The Optional
type looks like this:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
That is, an optional value is either .none
or .some
with an associated wrapped value. So, if you want an optional NWConnection
value that’s currently nil
, you can write this:
var connectionQ: Optional<NWConnection> = .none
However, that’s kinda ugly, so Swift has a bunch of syntactic sugar. Instead of the above you write:
var connectionQ: NWConnection? = nil
where NWConnection?
is shorthand for Optional<NWConnection>
and nil
is shorthand for Optional<NWConnection>.none
.
Note One nice thing about this is that optionals are not limited to pointer types. You can, for example, have an optional integer, written as Int?
.
Swift has a lot of sugar surrounding optionals. This makes sense when you consider its background. One key goal of Swift was good interoperability between it and C, Objective-C, and now C++. Those languages are full of pointers that might be optional, and writing out the optional syntax in full each time would be really clunky.
The most common sugared forms you’re likely to encounter are:
-
Optional types When writing a type T
, T?
is shorthand for Optional<T>
.
-
Optional force unwrapping When referencing an optional value, vQ!
is shorthand for:
switch vQ {
case .none:
fatalError()
case .some(let v):
return v
}
-
Optional chaining When referencing a member of an optional value, v?.m
is shorthand for:
switch vQ {
case .none:
return Optional<M>.none
case .some(let v):
return Optional<M>.some(v.m)
}
-
A let
clause in an if
or guard
statement I explained its expansion above.
But really this is just touching the surface. There’s a world of sugar beyond what I’ve explained above.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] You can go much further than that. For example, you can drop the NWEndpoint
because Swift can infer that from the value you’re binding to:
if let browseResult = browseResult,
case .service(name: let name, type: _, domain: _, interface: _) = browseResult.endpoint {
title = "Join \(name)"
}
In modern Swift you can simplify the first clause:
if let browseResult,
case .service(name: let name, type: _, domain: _, interface: _) = browseResult.endpoint {
title = "Join \(name)"
}
But you can also use the optional pattern to get rid of that first clause entirely:
if case .service(name: let name, type: _, domain: _, interface: _)? = browseResult?.endpoint {
title = "Join \(name)"
}
[2] There’s also some ABI stuff. For example, Swift knows that an object reference can’t be 0 so, at runtime, it encodes an optional object reference in the same way as Objective-C does, using 0 to reference the .none
case.