Let’s create a new playground, and
import Foundation at the top.
As we've hinted at previously,
importing a module makes its type available in the current file. Unlike a C
#include compiler directive, there are no header sources copied into the current file. Instead, each module defines what is exports, which we'll cover another week.
First, we'll cover
var url:URL? = URL(string:"https://www.apple.com") //https://www.apple.com
The first thing to notice is that the
init(string: method does not guarantee it will create a
URL from the
String must be correctly-formatted in order for this to work. Let’s see it fail, by making the url invalid, by using a space character:
var url:URL? = URL(string:" ") //nil
This means in code, you cannot assume that creating a
URL will succeed, so please don’t force-unwrap the optional. However, it does mean that if you require a
URL in your API, it means validation will have already taken place.
That works for creating a
URL to the internet.
var url:URL = URL(fileURLWithPath:"/users/ben/Documents/Swift 14 Foundation.md")
URL’s to files need to be constructed specially if you already have a path
Notice that we’re making a pretty big deal out of the fact that one is a file and the other is not.
var url:URL? = URL(string:"file:///users/ben/Documents/Swift%2014%20Foundation.md")
You can create a file URL with the
string: init function, but you’ll need to make sure you include the correct scheme, leave out the domain, and convert the spaces to precent escape encoding, so in other words, if you need to take a string path from another API, use the
init function to convert.
There is a property on the
.isFileURL, which will directly let you know if a
URL is actually a file. This is preferred over, for instance, using
.hasPrefix("file:///") on a
var isAFile:Bool = stringURL.hasPrefix("file:///") //anti-pattern var isAFile:Bool = url.isFileURL // thumbsup
For working with paths,
URL knows how to pull out path components into an array:
var pathComponents:[String]? = URL(string:"https://developer.apple.com/wwdc/live")?.pathComponents // ["/", "wwdc", "live"]
Or read the extension:
var fileExtension:String? = URL(fileURLWithPath:"/video.mp4").pathExtension
It also knows how to modify a url to add more path components:
var urlWithPath:URL? = URL(string:"https://developer.apple.com/wwdc/live") try! urlWithPath?.appendPathComponent("slides") urlWithPath //https://developer.apple.com/wwdc/live/slides
Many platforms rely on the use of strings to refer to files or internet resources, but
Foundation provides a custom value type,
URL. When importing a String from another source that should be represented with a URL, convert them immediately, leaving the interior of your code to work with this canonical type.
URLComponents - a Builder pattern
var scheme:String? = url.scheme var host:String? = url.host var query:String? = url.query
URL struct does provide read-only properties for extracting the pieces of a
query in this example. But they don’t allow direct setting, so let’s take a look at a companion struct,
var components = URLComponents() components.scheme = "https" components.host = "developer.apple.com" components.path = "/wwdc/live" components.user = "jonny" components.password = "1P40n3" components.queryItems = [URLQueryItem(name: "skip", value: "20"), URLQueryItem(name: "search", value: "Best Practices")]
URLComponents is what’s known as a "Builder" pattern. It’s an object whose properties are configured one at a time, and then it produces a manufactured object, a
URL. This lets us forget about formatting, and set each field directly.
let url:URL = components.url //https://jonny:1P40n3@developer.apple.com/wwdc/live?skip=20&search=Best%20Practices
Then let the components type take care of creating a correctly-formatted
URL for me:
Notice that the query parameters automatically got percent escaping for the right characters. Also notice that the query items are an
Array, not a
Dictionary, since the URL spec does not prevent a query key from appearing more than once.
One might even imagine that instead of passing
URL’s through various layers of an app, each one tacking on different fields with very carefully calculated string offsets, only the
URLComponents would be passed around, with each layer of the app modifying it to its heart’s content, only to be turned into a perfectly formatted
URL at the very end. In short, this is a grand slam for value types in Swift over the Class / Mutable Class Obj-C architecture.
Foundation provides basic date/time-types. There is a
Date, which represents an absolute position in time:
let now:Date = Date()
The empty initializer gives us "now".
We can also construct a
Date with a Unix system time:
let someSpecificTime = Date(timeIntervalSince1970:4567.0)
By using the word
init method’s intention is to make
Dates after 1970 have a positive value, and
Dates before negative.
Similarly, we construct a date moments into the future using:
let nearFuture = Date(timeIntervalSinceNow:0.4)
We can compute the
TimeInterval between two
Dates, which is simply a
typealias of a
now.timeIntervalSince(Date(timeIntervalSinceNow: 10.0)) //-10.004
You’ll notice you don’t quite get a perfect "10.0" because it takes milliseconds for the playground to move between these computations.
And again, the values are positive if the receiver is after the argument date, negative if it is before the argument date.
Alternatively, if we need to add small time intervals to a given date, Date provides simple math operations:
Date can also add small time intervals:
var nearFuture = now.addingTimeInterval(0.4) nearFuture.addTimeInterval(0.2)
I recommend you pretty much never do this, however, because a
Date is just a time interval since 1970. Since clocks are constantly changing to try to adjust for various political time changes and minute variations in the inaccuracy of our conventions, like 365-day years, or 24-hour days, merely adding time intervals to dates will not always get you what you want.
Just as we had a
URL to represent a single fully-composed URL, and used
URLComponents to create them, we’ll use
DateComponents to build
var components = DateComponents() components.year = 2016 components.month = 3 components.day = 1 components.hour = 17 components.minute = 32 components.second = 12 components.calendar = Calendar.init(identifier: .gregorian) components.timeZone = TimeZone.current let date = components.date //"Mar 1, 2016, 5:32 PM"
This constructed a Date for us, without our having to do any math, or know anything about leap years. Notice that we needed to get a
Calendar to make the components work. This is necessary, because although a single point in time represented by a
Date is the same in each
Calendar system, the way to break down the years, months, days, etc… are not the same.
Unlike arrays which use 0-based indexes,
DateComponents is 1-based. In other words, Jan 1, has
.month = 1 .day = 1
This means that we won’t need to alter any numeric values from when converting from the user’s writing or to display.
`DateComponents` are also useful in performing math operations on `Date`s. var difference = DateComponents() difference.day = -1 let future = gregorianCalender.date(byAdding: difference, to: date) //"Feb 29, 2016, 5:32 PM"
Here, we create a blank set of
DateComponents, then set a -1 for the
.day. Then we ask the calendar to perform the math, giving us a new
Date. Without needing to know anything about leap years, Foundation has provided us with the correct date before. In general, using components and Calendar-based math in this way helps us entirely avoid Date math problems, like leap years, and daylight savings time.
Today we learned the Foundation module, which ships with Swift, has many standard types used for building representing real values. Among those are
URLs, which represent both files and web resources, and
Dates which represent moments in time. Both have a
Components companion type which provide a "builder" pattern. The components provide direct meaningful access to the meaningful pieces of the larger types, and assemble one correctly on demand.
A standard library with value types for representing real-world values, now that's Swift!