The Swift Programming Language is a great book filled with incredibly well-written descriptions and clever examples, and I very highly recommend it. It's divided into three sections: "A Swift Tour," a language guide, and the formal grammar. This document falls in-between the tour and guideβit's a brief description of all the interesting, surprising, and unexpected aspects of the language that I came across while reading the book.
It isn't a recap of the entire book; the obvious, simple, and expected aspects of it are not mentioned here.
This summary is divided up into the same sections as the language guide, and follows its order exactly. Each subsection falls under one of three categories:
- β Important to remember
- β Required further insight
- π‘ Random thought/comment
For those without hyperlinks, I had nothing to add.
A Swift Tour
The Basics
Basic Operators
Strings and Characters
Collection Types
Control Flow
Functions
Closures
Enumerations
Classes and Structure
Properties
Methods
Subscripts
Inheritance
Initialization
Deinitialization
Automatic Reference Counting
Optional Chaining
Type Casting
Nested Types
Extensions
Protocols
Generics
Access Control
Advanced Operators
Disclaimer: all direct quotations from The Swift Programming Language are presented here in
blockquotes
and are used strictly for non-commercial, educational purposes under Fair Use (17 U.S.C. Β§ 107). I am neither affiliated with nor endorsed by Apple.
Code written at global scope is used as the entry point for the program.
That's awesome! But what happens if you have two source files in a project and attempt to build & run it in Xcode?
It turns out that if you have
more than one source file, one must be named main.swift
and will be used as
the entry point. Any other file with top-level code will raise error: expressions are not allowed at the top level
.
In fact, you can remove the @UIApplicationMain
attribute from an iOS app
and create a main.swift
file with nothing but:
UIApplicationMain(C_ARGC, C_ARGV, NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))
and your app will still run!
enum Rank: Int {
case Ace = 1
case Two, Three, four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
[β¦]
If you were to provide explicit raw values of 1
for the first case and 3
for the second case, what would the third be?
enum Test: Int {
case A = 1
case B = 3
case C // equals 4
}
Implicit raw values are always incremented by 1 from that of the value before it. An error will occur if a value is auto-incremented into one already used.
Modify the
anyCommonElements
function to make a function that returns an array of the elements that any two sequences have in common.
This wasn't an immediately obvious solution:
func anyCommonElements<
T, U where T: Sequence, U: Sequence,
T.GeneratorType.Element: Equatable,
T.GeneratorType.Element == U.GeneratorType.Element>
(lhs: T, rhs: U) -> [T.GeneratorType.Element] {
var commonElements: [T.GeneratorType.Element] = [] // [T.GeneratorType.Element]() doesn't seem to work
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
commonElements.append(lhsItem)
}
}
}
return commonElements
}
[β¦] you should avoid using keywords as names unless you have absolutely no choice.
When will this ever happen? Should the backticks feature even exist? I guess `class` is a little better than clazz at least.
Use
UInt
only when you specifically need an unsigned integer type with the same size as the platform's native word size. If this is not the case,Int
is preferred, even when the values to be stored are known to be non-negative.
The rules for combining numeric constants and variables are different from the rules for numeric literals. The literal value
3
can be added directly to the literal value0.14159
, because number literals do not have an explicit type in and of themselves. Their type is inferred only at the point that they are evaluated by the compiler.
let http404Error = (404, "Not Found")
Does convention dictate that this variable should instead be named
HTTP404Error
?
Here's
an example in the frameworks.
Both if let
and if var
can be used for optional binding, but the former
seems to be much more prevalent.
8 % 2.5 // equals 0.5
In this example,
8
divided by2.5
equals3
, with a remainder of0.5
, so the remainder operator returns aDouble
value of0.5
.
Would the remainder of two Float
s also return a Double
?
Nope:
let x: Float = 1.5
let y: Float = 1
let z = x % y // of type Float
If you try Float(1.5) % 1
you'll also get a Float
because Swift will infer
the 1
literal to be a Float
in this context. Pretty neat! But if you try
this:
let x: Float = 1.5
let y: Double = 1
let z = x % y
You'll be reminded that Swift doesn't implicitly convert types for you.
The closed range operator
(a...b)
defines a range that runs froma
tob
, and includes the valuesa
andb
.
What happens if a
and b
have the same value? It turns out that the loop will
execute once, not twice. So for i in 1...1
would be a redundantly silly way to
say "do this once."
Thanks to type inference, you don't need to specify the type to be stored in the array when using [the repeated value] initializer, because it can be inferred from the default value:
var anotherThreeDoubles = [Double](count: 3, repeatedValue: 2.5)
After thinking about it for a while, I believe this to be a mistake. Using a
repeated value of 1
will still yield a [Double]
since it's explicitly stated
in the initializer. They probably intended to use Array(count: 3, repeatedValue: 2.5)
, at which point the above quotation is true. I submitted
this as an issue on their bug reporter.
When used inside a loop statement,
break
ends the loop's execution immediately.
The following loop has an interesting (although possibly obvious) property:
var i: Int
for i = 0; i < 10; ++i {
// if i == 9 { break }
}
println(i)
It executes 10 times either way, but if you uncomment the break
, it will print
9
instead of 10
.
You can opt out of this behavior by writing an underscore (_) instead of an explicit external name when you define the parameter.
Variadic parameters simply appear within a function's body as a typed Array
βno
va_list
or va_args
.
The ability to associate values with members of an enumeration is a language feature I've never even heard of before, so this section is definitely worth reading twice.
All structures have an automatically-generated memberwise initializer, which you can use to initialize the member properties of new structure instances.
The identical to operator (===
) checks if two variables or constants refer
to the same class instances.
A String
is passed around by reference behind the
scenes, and two values may even lazily refer to the same reference. How might
===
behave for two value types with the same value but different underlying
references?
It turns out that there's no way to play around with this, because ===
only
works for types conforming to the AnyObject
protocol, and non-class type 'String' cannot conform to class protocol 'AnyObject'
. Interesting to think
about though.
If you assign a value to a property within its own
didSet
observer, the new value that you assign will replace the one that was just set.
Good thing this doesn't cause an infinite didSet
loop!
Global constants and variables are always computed lazily.
Imagine the effect on startup time if you had a lot of global constants or
variables. It would be instinctive to prefix them all with lazy
, but
luckily this is already the default and you don't even have to think about it.
The fact that structures and enumerations can define methods in Swift is a major difference from C and Objective-C.
Within the body of a type method, the implicit
self
property refers to the type itself, rather than an instance of that type.
Similar to method or operator overloading, subscript overloading is the term for defining more than one subscript on a type.
The main difference between classes and other types in Swift is that they have reference, rather than value, semantics. The other major difference is that they allow inheritance.
An overridden subscript for
someIndex
can access the superclass version of the same subscript assuper[someIndex]
from within the overriding subscript implementation.
You can present an inherited read-only property as a read-write property by providing both a getter and a setter in your subclass property override. You cannot, however, present an inherited read-write property as a read-only property.
Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created.
This requirement is mostly likely just an implementation detail and may be removed at some point in the future.
The process of an initializer calling another is referred to as initializer delegation.
If you define a custom initializer for a value type, you will no longer have access to the default initializer (or the memberwise initializer, if it is a structure) for that type.
Although you write
return nil
to trigger an initialization failure, you do not use thereturn
keyword to indicate initialization success.
Using init?
to indicate that initialization can fail is an effective way to
handle errors. It returns a Type?
, implying that either a value or "nothing"
was initialized. But when would init!
be useful? Implicitly-unwrapped
optionals indicate that you can be confident that the value you're currently
working with is not nil
without having to check it, but that it may have been
nil
at some point in its lifetime. Since you're working with a value from the
very beginning of its lifetime when you obtain it from an initializer, there
aren't many use cases for init!
.
It likely exists to help out with the Objective-C framework transitions to avoid having to manually check every single converted initializer, since "this thing might be nil but probably isn't" is how Objective-C works by default.
One other use case is overriding a failable initializer with one that you know
will never fail, but other than that init!
should probably be avoided when
possible.
[β¦] the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation.
Here's an example of how a strong reference cycle can be created by accident.
This is a very important concept in ARC, and one that's easy to completely gloss over or forget about entirely. StrongReferenceCycles.playground contains the sample code modified to work in a Playground, because the best way of learning is to see for yourself firsthand and tinker.
[unowned self]
can be read asβ¦
"capture self as an unowned reference rather than a strong reference".
[β¦] the result of an optional chaining call is always an optional value, even if the property, method, or subscript you are querying returns a non-optional value.
An example of using the postfix increment operator on a chained optional is given:
testScores["Bev"]?[0]++
But where should ++
go for prefix incrementing? Is it even possible?
Swift's type checker is able to deduce that
Movie
andSong
have a common superclass ofMediaItem
, and so it infers a type of[MediaItem]
for thelibrary
array:
Sick!!!
is
is referred to as the type check operator. Likewise, as
is the type
cast operator.
This is a very common and helpful pattern when working with an Array
whose
type is known to be different than that of the instance (e.g., an NSArray
):
for movie in someObjects as [Movie] { println("Movie: '\(movie.name)', dir. \(movie.director)") }
Retroactive modeling is the term for
extending types for which you do not have access to the original source code.
An example is given extending a Rect
structure with
init(center: Point, size: Size)
. This is clearly for convenience purposes
since all it does is compute the value of the expected origin
argument and
delegate to that initializer instead. However, no convenience
keyword is
present. This is because the concept of convenience initializers only truly
applies to classes, and value types must rely on extensions to achieve the same
effect.
Always prefix type property requirements with the
class
keyword when you define them in a protocol. This rule pertains even though type property requirements are prefixed with thestatic
keyword when implemented by a structure or enumeration.
For a placeholder type T
in a function parameter,
The placeholder type represented by the type parameter is replaced with an actual type whenever the function is called.
It is traditional to use the single-character name
T
for the type parameter. However, you can use any valid identifier as the type parameter name.
The
topItem
property returns an optional value of typeT
. If the stack is empty,topItem
returnsnil
; if the stack is not empty,topItem
returns the final item in theitems
array.
Optionals are great!
The section on type constraints details a unique language feature and is definitely worth reading a second time.
Associated types are a concept not often found in other languages, so the section on it is worth reading carefully.
A where clause enables you to require that an associated type conforms to a certain protocol, and/or that certain type parameters and associated types be the same.
This must be important, because it's the only fully-italicized sentence in the entire book:
Access levels in Swift follow an overall guiding principle: No entity can be defined in terms of another entity that has a lower (more restrictive) access level.
"To hide implementation details" is the best description of the concept of private I've ever read. It's concise yet leaves no room for ambiguity.
The access control level of a type also affects the default access level of that type's members.
An override can make an inherited class member more accessible than its superclass version.
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() { super.someMethod() } }Because superclass
A
and subclassB
are defined in the same source file, it is valid for theB
implementation ofsomeMethod
to callsuper.someMethod()
.
Although obvious based on the definition of private
, this is radically
different from other programming languages.
So ampersands have two usesβoverflow operators and inout parameters.
Are overflow operators (&+
, &-
, &*
, &/
, &%
) unique to Swift? They're
a clever ideaβoverflow is a silent killer!
Division or remainder by zero causes an error, but equals zero if the overflow operators are used instead. Why?
Operators combining assignment with another operation are called compound assignment operators.