Curious about what’s new in Swift 3? In this second part of a three-post series, Daniel discusses Functions & Closures, Collections, and Living with Guidelines.
You can find the other parts here:
Functions And Closures
Removing currying function declaration syntax (Proposal 0002)
Proposal 2 in Swift Evolution was to remove the currying function declaration syntax. That did not mean that currying was going away; it just means that this particular way of declaring the syntax was.
Up until now, you could declare a function that you wanted to use by currying with two sets of parentheses:
func curried(x: Int)(y: String) -> Float {
return Float(x) + Float(y)!
}
The first set of parentheses in this example is for x
, and the second set is for y
. What is missed is that if you call this function and pass in an x
, what you are getting back is a function. In this proposal, this intent is captured more clearly by insisting on using this syntax:
func curried(x: Int) -> (String) -> Float {
return {(y: String) -> Float in
return Float(x) + Float(y)!
}
}
When you pass in an x, what you are getting back is a function that takes a string and returns a float.
Flattening the function type of unapplied method references (Proposal 0042)
This proposal feels a bit technical, but it makes it easier to use functions in maps that you might not have been able to use before:
struct Type {
var x: Int
func instanceMethod(y: Int) -> Int {
return x + y
}
}
Inside of the Type
struct, we have a method named instanceMethod
that takes an Int
and returns an Int
. If you want to assign this method to f
, you would do it like this:
let f = Type.instanceMethod
Surprisingly, this is the Type
of f
, and it might not be what you expect it to be:
let f = Type.instanceMethod // f : (Type) -> (y: Int) -> Int
In this proposal, the type of f
has been flattened:
let f = Type.instanceMethod // f : (Type, y: Int) -> Int
Type
is the first parameter, and you can use subsequent parameters without having to reach inside that type.
Move @noescape and @autoclosure to be type attributes (Proposal 0049)
Other proposals that clean up some aspects of functions is that the @noescape
and @autoclosure
’s have been moved from decorating the variable to decorating the type:
func f(@noescape fn : () -> ()) {}
Here @noescape decorating the function fn
, it is moved inside to decorating the function type (something that takes nothing and returns nothing):
func f(fn : @noescape () -> ()) {}
Similarly, @autoclosure
has been moved inside as well:
func f(fn : @autoclosure () -> ()) {}
Limiting inout capture to @noescape contexts (Proposal 0035)
There is a subtly with inout
capture that is been addressed in this proposal. The contract of a parameter that has been declared inout
is that we can adjust the value of the variable that is being passed into us inside of the function. It is important that we limit the scope of how long these things live to not get unexpected behavior.
This proposal limits the inout
capture to @noescape
context:
func capture(f: () -> ()) {}
func example( x: inout Int) {
capture { _ = x }
}
Here is a function capture
which takes as its parameter a function that takes nothing and returns nothing. We use capture
inside of this function named example
. We pass it x
(an Int
that is marked inout
), and we use it inside of a capture. This might seem to be illegal (and initially it was), but by default f
is non-escaping.
Make non-escaping closures the default (Proposal 0103)
In the subsequent proposal, @noescape
is the default for closure; this is legal. You can explicitly label a function as being escaping in this way:
func capture(f: @escaping () -> ()) {}
func example( x: inout Int) {
capture { _ = x }
}
In this case, this is illegal because inout
capture is limited to non-escaping. Another way to fix this would be to use this:
func capture(f: @escaping () -> ()) {}
func example( x: inout Int) {
capture {[x] _ = x }
}
That syntax may look odd to you. It is not an array of element x
; it is a capture list. We are making a strong copy of x
and passing it into the closure.
Standardize function type argument syntax to require parentheses (Proposal 0066)
If we go back to the syntax from our curried example, code like this is often hard to parse:
func curried(x: Int) -> String -> Float {
It is an Int
to a String
to a Float
. The arrow is right associative. To make this clearer, function type argument syntax now requires parentheses. Because String
is the parameters for the return functions, it has to go in parentheses:
func curried(x: Int) -> (String) -> Float {
Similarly, you will see function types where the parentheses have to go around:
(Int) -> Int
(Int, Int) -> Int
((Int, Int)) -> Int
In this case, an Int
, a pair of Int
s, and in the final case, because of the second parentheses required around a tuple, around a tuple of two Ints
.
Defaulting non-Void functions on unused results (Proposal 0047)
One of the cornerstones to Swift is making your code explicit. In this case, I have a function that returns something:
func f() -> T {}
f()
When I call it, am I using the return value or not? With this proposal, you have to let the compiler know that you know it returns something, and you are explicitly ignoring it. You can do by just assigning it to underscore:
func f() -> T {}
_ = f()
Sometimes you create functions where the point is the side effects and not the return value. In that case, you can label the function as @discardableResult
, and then people do not have to assign that to underscore to something else:
@discardableResult func f() -> T {}
_ = f()
Remove @noreturn attribute and introduce an empty Never type (Proposal 0102)
There are functions that may not return. For example, they might have a fatalError()
. Instead of the @noreturn
attribute, there is a Never
type - you can return the Never
type here:
func noReturn() -> Never {
fatalError()
}
If you have this fatalError()
, you do not expect the noReturn()
function to return, and you assign it to return a Never
type. If we have a function that returns an Int
inside of the guard
clause, we can call that noReturn()
:
func noReturn() -> Never {
fatalError()
}
func pickPositiveNumber(below limit: Int) -> Int {
guard limit >= 1 else {
noReturn()
}
return rand(limit)
}
Because we expect it not to return anything because it returns a Never
type, we do not have to have a return statement inside of the guard
.
Abolish implicitly unwrapped optional type (Proposal 0054)
If you are working with storyboards, you know that outlets tend to have this implicitly unwrapped optional type, and that was to simplify code because of the life cycle of when outlets are connected and instantiated. They start their life as nil
when the view controller is loaded; when the storyboard is connected, and everything is wired up, that is when they actually get their value. We had this implicitly unwrapped optional type.
That formal type is going away in Swift 3. Again, it was hard to reason about. The syntax remains, but the syntax has new meaning:
let x: Int! = 5
let y = x
let z = x + 0
x
looks like it is an implicitly unwrapped optional type; it has that exclamation point after it. The idea is if you assign x
to y
, the type of y
is an optional Int because we were not forced to unwrap it:
let y = x // y: Int?
But here, the type of z is actually an Int because we are forced to unwrap it to add the zero to it:
let z = x + 0 // z: Int
The type is going away, and the behavior is being normalized.
Remove type system significance of function argument labels (Proposal 0111)
This proposal is interesting because it is in a transitional period. The proposal was to remove the type significance of function argument labels:
var op : (lhs : Int, rhs : Int) -> Int
lhs
and rhs
would not have significance because this is preparing the way for something that will happen in Swift 4. The core team came back and asked that it be changed:
var op : (_ lhs : Int, _ rhs : Int) -> Int
Underscores are used for the external argument and significance can be reintroduced later.
Naming functions with argument labels (Proposal 0021)
Here we have an example from UIView where we have three methods with the exact same name - they are all called insertSubview
:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view; UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view; UIView, belowSubview siblingSubview: UIView)
}
In Objective-C, the name would include the subsequent parts; they would all be part of the selector. That has not been the case in Swift until Swift 3 where now you can name the functions using the argument labels. No one is confused when we call the methods:
someView.insertSubview(view: view, at: 3)
someView.insertSubview(view: view, aboveSubview: otherView)
someView.insertSubview(view: view, belowSubview: otherView)
Because we call them using different signatures, there is no confusion which version of insertSubview we want when we are calling it.
This proposal takes care of this ambiguity. Which one do we mean when we say:
let fn = someView.insertSubview // ????
We can specify further by including the argument labels as part of the signature:
let f1 = someView.insertSubview(view: at:)
let f2 = someView.insertSubview(view: aboveSubview:)
let f3 = someView.insertSubview(view: belowSubview:)
f1
, f2
, and f3
clearly refer to the three different functions.
Referencing the Objective-C selector of a method (Proposal 0022)
In Swift, we have been passing in selectors as strings. In Swift 3, we now pass them in using #selector
, which looks like the @selector from Objective-C:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view; UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view; UIView, belowSubview siblingSubview: UIView)
}
let f2 = someView.insertSubview(view: aboveSubview:)
let sel = #selector(UIView.insertSubview(view:aboveSubview:))
And we use this new naming convention, and we can specify which of these functions we mean.
Referencing the Objective-C selector of property getters and setters (Proposal 0064)
This companion proposal allows us also to specify a getter and a setter. For the property UIView.center
, we can specify the getter and setter using #selector
:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view; UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view; UIView, belowSubview siblingSubview: UIView)
}
let getr = #selector(getter: UIView.center)
let setr = #selector(setter: UIView.center)
Referencing Objective-C key-paths (Proposal 0062)
Similarly, we can use #keypath
to specify the key path as we walk down this tree:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view; UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view; UIView, belowSubview siblingSubview: UIView)
}
let path = #keyPath(UIView.superview.backgroundColor)
let color = someView.value(forKeyPath: path)
From a UIView
to its superview
to its backgroundColor
. Then, we can use the path
to retrieve a value.
Collections
Remove the ++ and – operators (Proposal 0004)
Some of the simplest proposals cause the greatest fuss. In this one, we remove the increment and decrement operators. New programmers often make mistakes with pre-increment and post-increment, and pre-decrement and post-decrement. This proposal forces us to use the += instead of the ++:
x += 1
There are times that this is inconvenient. For example:
var count = 0
func increaseCount() -> Int {
return count++
}
Where we return count++
: that is not available to us anymore. We could use this wordier and harder to understand method:
func increaseCount() -> Int {
count += 1
return count - 1
}
Where we increase count and then, we return the decreased amount back. Instead of returning count - 1
, if we prefer to return count
, we can use this technique to defer the increasing the count until after the return:
func increaseCount() -> Int {
defer {count += 1}
return count
}
Remove C-style for-loops with conditions and incrementors (Proposal 0007)
Once you remove the ++, you have to reconsider what you are going to do with standard C-style for-loops:
for var i = 0 ; i < 10 ; i++ {
print (i)
}
In Swift 3, those go away. Instead, we use the range base for-loops:
for i in 0 ..< 10 {
print (i)
}
A new model for collections and indices (Proposal 0065)
This proposal introduces a new model for Collections and Indices and is quite involved. The simplest change is that in the past we have gotten an index, and then we have incremented by asking the index for it is successor:
let xs = [10, 20, 30, 40]
let i = xs.startIndex
let next = i.successor()
Now it is the collections’ responsibility to move the iterator:
let xs = [10, 20, 30, 40]
let i = xs.startIndex
let next = xs.index(after: i)
Some of the Collection related proposals that we will not be going into include: “Constraining AnySequence.init”, “Adding a public base property to slices”, “Adding prefix(while:) and drop(while:) to the standard library”, and “Adding sequence(first: next:) and sequence(state: next:) to the standard library”.
Add first(where:) method to Sequence (Proposal 0032)
A handy addition to Swift 3 is that we will be adding first(where:) method to Sequence
:
extension Sequence {
public func first(where predicate: @noescape (Self.Iterator.Element) throws -> Bool) rethrows -> Self.Iterator.Element? {
for elt in self {
if try predicate(elt) {
return elt
}
}
return nil
}
}
Sequence, you can see what first where
looks like, we pass in a predicate, and then we search through the Sequence for the first instance for which the predicate is true
, and return that. If there is none, we return nil
.
Add a lazy flatMap for sequences of optionals (Proposal 0008)
Some more housekeeping proposals, there was a Lazy version of some flatMaps, but not this one. A Lazy flatMap has been added for Sequences of Optionals.
Change IteratorType post-nil guarantee (Proposal 0052)
The IteratorType’s post-nil guarantee has been changed: once you hit your first nil
, you have to keep returning nil
.
Living with Guidelines
Scooped access level (Proposal 0025)
In Objective-C, we did not explicitly have the notion of public
and private
. If we did not want to expose a method or a property as public
, we would not put it in the header file. In Swift, we have to be more explicit, and up until now, we have had three different access levels: public
, internal
(default value), private
. Internal means visible within a module; Public means visible to anybody that imports a module. Private is the one that does not always mean what people think it should mean. Private has meant only visible within this file.
In Swift 3 private is going to take on a new meaning: it is going to mean private to this type. Swift 3 introduces a new level: fileprivate
(what private used to mean in Swift 2). If you have used private in Swift 2, you now will use fileprivate
to express the same thing and private will be even more restrictive.
Make optional requirements Objective-C only (Proposal 0070)
Something else that was common in Objective-C but not in Swift is that protocols could have optional
methods:
protocol NSTableViewDelegate {
optional func tableView(_: NSTableView,
viewFor: NSTableColumn,
row: Int) -> NSView?
}
Many of the Cocoa and Cocoa Touch protocols have optional methods that may or may not be implemented by classes conforming to these protocols. This notion of optional methods within a protocol violates the idea in Swift of being clear and knowing what to expect. If a method is part of a protocol, to conform to that protocol you should implement that method.
This can cause difficulty when interrupting between Swift and Objective-C where the expectations are different. This proposal allows for optional methods within a protocol as long as the protocol itself is marked @objc
and the method is also marked @objc
:
@objc protocol NSTableViewDelegate {
@objc optional func tableView(_: NSTableView,
viewFor: NSTableColumn,
row: Int) -> NSView?
}
In other words, optional methods within a protocol are for that specific purpose.
Replace typealias keyword with associatedtype for associated type declarations (Proposal 0011)
In Swift 2 the word typealias
has had two different meanings. In this example, the two typealias
annotations are used for two very different purposes:
protocol Prot {
typealias Container : SequenceType
}
extension Prot {
typealias Element = Container.Generator.Element
}
The second one is saying that Element
will be the name we use for Container.Generator.Element
. It is a convenience name we will use within this protocol. Container
, on the other hand, is not a typealias
. It is a placeholder for something that will be specified later on. All we know is that it must conform to sequence type. Container
is an associatedtype
and not a typealias. In Swift 3 we will use the word associatedtype
:
protocol Prot {
associatedtype Container : SequenceType
}
extension Prot {
typealias Element = Container.Generator.Element
}
Typealiases in protocols and protocol extensions (Proposal 0092)
For a while, the attribute typealias
was not allowed within protocols. However, now that we have a clear meaning for what an associatedtype
is, this proposal allows us to reintroduce the word typealias
to mean what it has always meant, and now there is no confusion:
protocol Sequence {
associatedtype Iterator : IteratorProtocol
typealias Element = Iterator.Element
}
We are using two different words for two different concepts. In this case, Iterator
is an associatedtype
and Element
is a typealias
.
Generic type aliases (Proposal 0048)
Another typealias
proposal allows for generic typealiases:
typealias StringDictionary<T> = Dictionary<String, T>
typealias DictionaryOfStrings<T : Hashable> = Dictionary<T, String>
typealias IntFunction<T> = (T) -> Int
typealias Vec3<T> = (T, T, T)
typealias BackwardTriple <T1, T2, T3> = (T3, T2, T1)
In these examples, StringDictionary
takes a generic type T
and it specifies a Dictionary
that uses a key of type String
in the value of type T
. Whereas DictionaryofStrings
takes a key which is Hashable
of type T
and the value is a String
. IntFunction
is also generic in T
, and it defines a function that takes something of type T
and returns an Int
. Our Vec3
takes something of type T
and creates a vector that has three elements of that same type. This BackwardTriple
reverses the order of T1, T2, T3
. It is nice to have that expressivity in defining typealiases using generics.
Other related proposals that we will not look at in depth include: “Importing Objective-C Lightweight Generics” and “Expanding Swift Self to class members and value types”.
Mutability and foundation value types (Proposal 0069)
For historic reasons most of Foundation are classes in Objective-C, but as we look at them in Swift, it makes sense for some of them to be value types:
let myDate = Date()
let myLaterDate = myDate.dateByAddingTimeInterval(60)
For instance, instead of NSDate
we can look at myDate
being an instance of Date
and Date
should be a value type. Making it a value type makes it easier to reason about. In fact, as we do date arithmetic, when we ask for a date by adding a time we obtained a new instance of an NSDate
. The same is true here, as I create myLaterDate
by asking myDate
to give me a dateByAddingTimeInterval
- that gives me a new instance of a date with this new value.
Many of the Foundation Objects came in pairs NS something and then as Mutable that same thing. NSDate was not one of them but this proposal also looks at where we would add Mutability to these Foundation value types.
For instance, if we created this myOtherdate
, we could not mutate it because it was created as a let
:
let myOtherDate = Date()
myOtherDate.addTimeInterval(60)
If we make it a var then we would get these mutable methods:
var myOtherDate = Date()
myOtherDate.addTimeInterval(60)
myOtherDate.addTimeInterval
does not create a new instance of Date
but it mutates myOtherDate
in an expected way.
In Swift 3 you are going to see these foundation types reimagined as value types and using mutability where appropriate. When you create it with a let
value, you get the immutable version and when you create it with a var
you get the mutable version, as we have done with arrays and dictionaries in the Swift standard library.
Drop NS prefix in swift foundation (Proposal 0086)
In the Swift version of Foundation, we will often drop the NS prefix. You saw NSDate
became Date
. In general, we will drop NS prefix except if there is Objective-C specific classes such as NSObject
, or if there is platform specific classes such as NSUserNotification
. There is also some exceptions for classes during the transition to Swift. Dozens are going to drop the NS prefix (you can see many of them listed in the video, or the proposal link).
NSTask
will change to Process
(not Task) because there already exists a Process that it makes sense to roll the NSTask capabilities into.
Dozens will be hoisted. Here are two NSURLRequest
related classes: NSURLRequestCachePolicy
, NSURLRequestNetworkServiceType
The NS will be dropped, and they will be part of URLRequest
, but CachePolicy
and NetworkServiceType
will become the sub-type. You will see URLRequest.CachePolicy
and URLRequest.NetworkServiceType
.
Related to these are proposals to: “Fully eliminate implicit bridging conversions in Swift” and “Remove the Boolean protocol”.
Improved NSError bridging (Proposal 0112)
One of the improvements in Swift 2 is the introduction of Error Handling (the try
and catch
, throws
and rethrows
and throw
and do
). One of the problems that remained was the bridging to NSErrors:
- (nullable NSURL *) replaceItemAtURL:(NSURL *)url
options:(NSFileVersionReplacingOptions)options
error:(NSError **)error:
This API would have been brought in as this, transforming it from Objective-C to the new Swift Error Handling:
func replaceItem(at url: URL, options: ReplacingOptions = []) throws -> URL
That left some of the information behind, and Swift 3 introduced these protocols to capture some of this missing information. For instance, you can now provide a localized error:
protocol LocalizedError : Error {
var errorDescription: String? { get }
var failureReason: String? { get }
var recoverySuggestion: String? { get }
var helpAnchor: String? { get }
}
The localization includes an error description, a failure reason, a recovery suggestion and even a help anchor.
A protocol extension stubs out each one of these methods to return nil
:
extension LocalizedError {
var errorDescription: String? { return nil }
var failureReason: String? { return nil }
var recoverySuggestion: String? { return nil }
var helpAnchor: String? { return nil }
}
This allows you to override the methods that you want to override in your conforming classes. It also shows you the Swift preferred way of getting around optional methods. We do not need optional methods and protocols because we can stub out default behavior.
A second protocol introduced was RecoverableError
:
protocol RecoverableError : Error {
var recoveryOptions: [String] { get }
func attemptRecovery(optionIndex recoveryOptionIndex: Int,
resultHandler handler: (recovered: Bool) -> Void)
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool
}
This includes recoveryOptions and methods for attempting recovery. Again, in the extension attemptRecovery is stubbed out to apply the handler to the information.
extension RecoverableError {
func attemptRecovery(optionIndex recoveryOptionIndex: Int,
resultHandler handler: (recovered: Bool) -> Void) {
handler(recovered: attemptRecovery(optionIndex: recoveryOptionIndex))
}
}
There are many other aspects of NSError Bridging that I encourage you to check out further.
Resources
- Proposal 0002 - Removing currying
func
declaration syntax - Proposal 0004 - Remove the
++
and--
operators - Proposal 0007 - Remove C-style for-loops with conditions and incrementers
- Proposal 0008 - Add a Lazy flatMap for Sequences of Optionals
- Proposal 0011 - Replace
typealias
keyword withassociatedtype
for associated type declarations - Proposal 0021 - Naming Functions with Argument Labels
- Proposal 0022 - Referencing the Objective-C selector of a method
- Proposal 0025 - Scoped Access Level
- Proposal 0032 - Add
first(where:)
method toSequence
- Proposal 0035 - Limiting
inout
capture to@noescape
contexts - Proposal 0042 - Flattening the function type of unapplied method references
- Proposal 0047 - Defaulting non-Void functions so they warn on unused results
- Proposal 0048 - Generic Type Aliases
- Proposal 0049 - Move @noescape and @autoclosure to be type attributes
- Proposal 0052 - Change IteratorType post-nil guarantee
- Proposal 0054 - Abolish
ImplicitlyUnwrappedOptional
type - Proposal 0062 - Referencing Objective-C key-paths
- Proposal 0064 - Referencing the Objective-C selector of property getters and setters
- Proposal 0065 - A New Model for Collections and Indices
- Proposal 0066 - Standardize function type argument syntax to require parentheses
- Proposal 0069 - Mutability and Foundation Value Types
- Proposal 0070 - Make Optional Requirements Objective-C-only
- Proposal 0086 - Drop NS Prefix in Swift Foundation
- Proposal 0092 - Typealiases in protocols and protocol extensions
- Proposal 0102 - Remove
@noreturn
attribute and introduce an emptyNever
type - Proposal 0103 - Make non-escaping closures the default
- Proposal 0111 - Remove type system significance of function argument labels
- Proposal 0112 - Improved NSError Bridging
Receive news and updates from Realm straight to your inbox