The Swift Programming Language (Swift 3.0.1) by Apple Inc (2016) (read in 2017)
Published by marco on
Disclaimer: these are notes I took while reading this book. They include citations I found interesting or enlightening or particularly well-written. In some cases, I’ve pointed out which of these applies to which citation; in others, I have not. Any benefit you gain from reading these notes is purely incidental to the purpose they serve of reminding me what I once read. Please see Wikipedia for a summary if I’ve failed to provide one sufficient for your purposes. If my notes serve to trigger an interest in this book, then I’m happy for you.
This is an incredibly detailed language specification for Swift and programming guide for iOS and OS X. The language itself is quite complex and includes so many programming features that it’s nearly dizzying. There are a lot of examples, as well as English-language translations of all of the examples. This leads to a lot of text, but you can get into the rhythm of skipping the few paragraphs after the examples to move on to the next concepts (especially if the code sample was abundantly clear).
The kitchen sink includes:
- Functional switch statements (matching)
- Inferred types
- Argument labels
- Very wordy Error-handling
- An unbelievable number of keywords:
autoclosure: required when the developer wants to avoid closing over certain variables and creating cycles (which the reference-counter in Swift cannot break on its own)
convenience: identifies initialization methods that are not required
defer: Swift’s version of
deprecated: An attribute to indicate when a feature was deprecated (see
discardableResult: functions are pure and results must be used; add this attribute to allow a result to be ignored
dynamic: trigger the compiler to use Objective-C–style method-dispatch
escaping: indicate that a lambda parameter is used outside of the called function
extension: add features to a type
infix: operator type
introduced: An attribute to indicate when a feature was introduced
lazy: explicitly indicate that a property initialization should be delayed until it is first called
obsoleted: An attribute to indicate when a feature was made obsolete (see
optional: a feature of a protocol that does not have to be implemented in the implementing type (it can be satisfied by an
postfix: operator type
prefix: operator type
required: identifies initialization methods that are not convenience
subscript: an indexer function (e.g.
testable: an attribute that subverts the type/member/function visibility so that a test framework has access to internals
unowned: use a weak reference that allows the owner to cascade-delete the object that has the unowned reference to it
weak: a classic weak reference: the reference will be set to
nilwhen the referenced object is deleted.
- two types of generic constraints
- and so on…
“Sometimes it is clear from a program’s structure that an optional will always have a value, after that value is first set. In these cases, it is useful to remove the need to check and unwrap the optional’s value every time it is accessed, because it can be safely assumed to have a value all of the time.
“These kinds of optionals are defined as implicitly unwrapped optionals. You write an implicitly unwrapped optional by placing an exclamation mark (String!) rather than a question mark (String?) after the type that you want to make optional.”
“A recursive enumeration is an enumeration that has another instance of the enumeration as the associated value for one or more of the enumeration cases. You indicate that an enumeration case is recursive by writing indirect before it, which tells the compiler to insert the necessary layer of indirection.”
These are pretty interesting. The arithmetic example is a very elegant way of implementing this behavior.
“Use a weak reference when the other instance has a shorter lifetime—that is, when the other instance can be deallocated first. In the Apartment example above, it is appropriate for an apartment to be able to have no tenant at some point in its lifetime, and so a weak reference is an appropriate way to break the reference cycle in this case. In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime.”
You can’t pass a struct or enum by reference, so you’re bound to value/ref usage at type-declaration time. That’s a pretty harsh restriction.
Deallocation is also immediate ([A]utomatic [R]eference [C]ounting takes care of it). This means that it is also predictable (unlike disposal/finalization in C#)
It also means, however, that allocation cycles can only be resolved with developer intervention
So….as usual, Swift solves it with multiple tools:
Weak = set to null
Unowned = cascade
“In systems that use garbage collection, weak pointers are sometimes used to implement a simple caching mechanism because objects with no strong references are deallocated only when memory pressure triggers garbage collection. However, with ARC, values are deallocated as soon as their last strong reference is removed, making weak references unsuitable for such a purpose.”
“You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.”
“However, there is a third scenario, in which both properties should always have a value, and neither property should ever be nil once initialization is complete. In this scenario, it is useful to combine an unowned property on one class with an implicitly unwrapped optional property on the other class.”
“Because capitalCity has a default nil value, a new Country instance is considered fully initialized as soon as the Country instance sets its name property within its initializer. This means that the Country initializer can start to reference and pass around the implicit self property as soon as the name property is set. The Country initializer can therefore pass self as one of the parameters for the City initializer when the Country initializer is setting its own capitalCity property.”
This is a very curious way of handling initialization. The ordering of parameter initialization determines at which point
self can be used.
“Swift requires you to write self.someProperty or self.someMethod() (rather than just someProperty or someMethod()) whenever you refer to a member of self within a closure. This helps you remember that it’s possible to capture self by accident.”
“Sometimes you know a throwing function or method won’t, in fact, throw an error at runtime. On those occasions, you can write try! before the expression to disable error propagation and wrap the call in a runtime assertion that no error will be thrown. If an error actually is thrown, you’ll get a runtime error.”