Swift's compiler is smart enough to take note of this nesting whenever ambiguity arises. This means we can use types nested inside multiple "container" types without any issues.
Even-lazier Lazy Definitions
We covered the lazy keyword in Bite #179. It turns out, we can be even lazier, and omit the extra closure altogether. (Still works the same).
lazyvarbackgroundView=SpecialBackgroundView()
Variadic Parameters
Objective-C supported these as well, but they were a bit of pain to work with there. In Swift, we simply add an ellipsis (...) to our function's definition, and we receive the arguments as a strongly-typed Swift** array**.
Today we'll continueourlookat interesting Swift features. Let's begin.
Type Matching with ‘is’
Try as we might, sometimes still we end up with an AnyObject or Any reference in our Swift code. We can ask if it is a given type or subtype using Swift's powerful pattern matching:
This technique has a few different applications, but one good example is capturing a value from a switchstatement inline:
enumTheme{caseDay,Night,Dusk,Dawnfuncapply(){// ...letbackgroundColor:UIColor={switchself{caseDay:returnUIColor.whiteColor()caseNight:returnUIColor.darkGrayColor()}}()// ... set backgroundColor on all UI elements}}
Today we'll look at acouplemore tips for working in Swift. This time, we'll focus on Swift properties. Let's dive in.
Property Observers
In Objective-C, we'd likely use Key-Value Observing to "know" when the value of a property on one of our objects changes.
KVO still works great in Swift. In many cases, we might be able to get away with Swift's (much cleaner) property observers:
structSpaceship{letname:StringvarcurrentSpeed:Int=0{willSet{print("About to change speed to \(newValue)")}didSet{ifcurrentSpeed>oldValue{print("Increased speed to \(currentSpeed)")}elseifcurrentSpeed<oldValue{print("Decreased speed to \(currentSpeed)")}}}}
Lazy Properties
"Lazy" initialization is a great way to put off expensive work until some later time in our code. Back in Objective-C, if we wanted to "lazily" initialize a property, we might go override it's getter, and check if a private instance variable is nil or not, then populate it if it's not, then return it. Whew!
In Swift, we can forget all of this and accomplish the exact same with a simple new keyword:
lazyvarcouchPotatoes=[String]()
We can also use this technique to lazily init our UI elements, neat!
Today we'll continue our look at interesting Swift features. Let's begin.
Multiple/Chained Optional Unwrapping
This is great when we've got multiple optionals to unwrap. We can even use unwrapped references from earlier in the statement in later parts of the statement. Neat!
ifletURL=NSURL(string:someURL),letdata=NSData(contentsOfURL:URL),letship=Spaceship(data:data){// use 'ship'}
Multiple Initialization
We can initialize multiple references in one statement (and one line). This works for when using both let and var:
We can simplify Array (and Dictionary) initstatements like this:
letspaceships:[Spaceship]=[]
To this:
letspaceships=[Spaceship]()
Semicolons: Gone but not forgotten!
Removing the need for trailing semicolons was one of Swift's most welcome features. However, they actually are present in Swift, and can serve a useful purpose. They'll let us write multiple statements on one line. This works great (for example) in guardstatements:
Optionals in Swift are a great way for us to represent either some value, or no value at all. Like many parts of Swift, they're actually built (mostly) in Swift itself. Today we'll pop the hood, and take a look at how they work. Let's get started.
We'll begin by heading over to the definition of the Optionalenum in the Swift headers. We can get here by pressing ⌘⇧O, then typing "Optional":
Right away we'll see that Optional types are merely an enum with an associated value. That associated value has a type, but it's a generic placeholder for whatever type we'll make Optional references to. The trailing-? syntax is merely syntactic sugar, for example:
String?
is simply a shorthand way of writing:
Optional<String>
Swiftenums really show their power here, we're able to represent a fairly high level concept, with this relatively simple construct. Next, let's look at initializing Optionals.
/// ...publicinit()publicinit(_some:Wrapped)
The first init statement handles Optionals with no value, the second handles the presence of some value. Additionally, Optional adopts NilLiteralConvertable, so all three of these statements mean the same thing:
Today we'll look at a fewmore interesting Swift tips and tricks. Let's get started.
Multiple Switch Cases
We'll start with a simple one. We can simplify messy switch statements by combining the similar cases. This is similar to how we can use multiple case statements in other languages, but a bit less wordy:
In Bite #159, we started looking at some syntactic shortcuts in Swift. Today we'll continue by looking at a few (perhaps) lesser-known Swift tricks, and their effects. Let's get started.
@autoclosure
This attribute can help save space when writing simple closures:
Now, the compiler will infer the curly braces {} around our statement:
cache("spaceships",cacheIf:ships.count>0)
private(set)
structSpaceship{private(set)varname="Untitled"}
With the private(set) declaration, we're telling the compiler this property has the default access-level of internal for reads, but writes can only happen in the source file where it's declared.
For frameworks, we can configure both the getter/setter explicitly:
When optimizing performance-critical code, dynamic dispatch can be our enemy.
If our class's properties and functions can be overridden by subclasses, we're going to pay the performance cost of an indirect call or access every time we use one of those functions or properties.
We can easily prevent this by declaring a property or functionfinal.
This way, any attempts to override it will result in a compile-time error. We can also declare a classfinal, which applies the final keyword to all the class's functions and properties.
Today we'll begin checking out some syntactic niceties available in Swift. These are tricks that can help make our code more readable, and can often make it easier to reason about. Let's get started:
Omitting Types
The Swift type system is pretty great. As long as the compiler can infer the type, we can simply leave it out when calling static functions:
This trick also works just as well for static properties:
letcell=ThemeTableViewCell(theme:.currentTheme)
Shorthand Argument Names
We can use a $x syntax to reference closure arguments:
spaceships.sort{$0.name>$1.name}
Trailing Closures
Many functions accept a closure as their last parameter:
funcchangeTheme(theme:Theme,completion:()->())
In these cases, we can call them with this shorter syntax:
Theme.changeTheme(dayMode){/* */}
Nil coalescing
The ??operator offers us a way to express a sort of "fallback" relationship between two statements. When the first statement is nil, it "falls back" to the second.
letcellTitle=trooper.nickname??trooper.troopID
At a technical level this returns either an unwrapped optional, or the value on the right, which can't be an optional.
Style is important when writing code. Following conventions and guidelines helps tell the story of our code to our team and our future-selves. Today we'll learn about SwiftLint, a tool from Realm that can help us enforce Swift conventions and best practices. Let's get started.
SwiftLint provides a downloadable install package on its Github page, but here we'll install using Homebrew. We'll run:
brew install swiftlint
Now, we can run swiftlint rules at the command line to see all the different convetions and standards that will be enforced. We can also run swiftlint lint in the root directory of our project to see what rules we're breaking right in the terminal.
Our next step is to add SwiftLint as a build phase to our project.
We'll head over to our project's settings, then to the Build Phases tab. We'll click the + button to add a** new “Run Script” phase**. We'll name it “SwiftLint” and give it the follow script content:
if which swiftlint > /dev/null; then
swiftlint
else
echo “Download SwiftLint: https://github.com/realm/SwiftLint"
fi
Now when we build our project, SwiftLint will let us know via regular Errors and Warnings in Xcode when there's something to fix.
We can configure how SwiftLint behaves in complete detail by creating a new file called .swiftlint.yml and putting it in the root directory of our project. We can fill out this file to customize (for example) which conventions are enforced:
disabled_rules:-colon-control_statement
We can disable rules “in-line” in our code with special comments:
// swiftlint:disable colonletnoWarning:String=""// No warning about colon placement// swiftlint:enable colonletyesWarning:String=""// Warning generated
Finally, SwiftLint can correct some violations (trailing_newline, trailing_semicolon, etc.). Just run swiftlint autocorrect.
Today we're going to talk about Error Handling. First though, a bit of a pitch: Great apps handle errors gracefully. Think of error handling as the dental flossing of creating apps. Sure, not the most exciting part of the job, but very important. Alright, let's dive in:
To allow one of our functions to throw an error, we add a throws keyword to its definition. Then we can create our own enum that inherits from the system's ErrorType.
Then anywhere inside our function, when something goes wrong, we can throw the appropriate error.
Elsewhere in our code, we can use a do/catch block to handle these errors. We'll try our dangerous code in the do block, then catch any errors below.
do{trydecryptDeathStarPlans()}catchBlueprintFileError.Interrupted{alert("Decryption Interrupted!",options:["Try Again","Cancel"])}catchBlueprintFileError.CorruptData{alert("Sorry, the file could not be read from disk.",options:["OK"])}catchBlueprintFileError.ShipBoardedByVader{transferFilesToDroid("R2-D2")alert("Ship is being boarded, "+"decryption will continue on R2-D2",options:["OK"])}catchleterror{alert("Decription Failed",options:["Try Again","Cancel"])}
Finally, let's look at how best to actually handle these errors. Every app is unique, and will need special consideration around how best to handle its errors. That being said, here's some guidelines that should apply in most cases, and are illustrated above:
Fail as gracefully as possible, and preserve as much of the user's work as possible.
If necessary, tell the user what happened in clear simple terms. (No jargon).
When possible, give the user a way to try the task again.
Handle all cases, even if unlikely, if it can go wrong, it will, for someone.