Project hygiene is very important in my opinion and it immediately shows you what type of developer you're working with. The Xcode theme I usually use for development highlights string literals in bright red, showing me when a string literal has made its way into the codebase.
String literals aren't bad. They're usually inevitable. What we want to avoid is clutter. For example, a developer new to the project shouldn't have to wander the codebase to find the API key of the Dark Sky API. And we certainly want to avoid multiple instances of the same string literal.
I'd like to illustrate how easy it is to remove the string and number literals from the RootViewController class by creating a simple, lightweight type that encapsulates the details of a request.
Creating a Dedicated Type
Create a new group and name it Models. Add a new file to the Models group by choosing the Swift File template from the iOS section. Name the file WeatherRequest.swift.


Define a structure and name it WeatherRequest. A value type is a perfect fit for this use case.
import Foundation
struct WeatherRequest {
}
The WeatherRequest struct defines three stored properties, baseUrl of type URL, latitude of type Double, and longitude of type Double. Because the WeatherRequest struct doesn't define an initializer of its own, it automatically receives a memberwise initializer.
import Foundation
struct WeatherRequest {
// MARK: - Properties
let baseUrl: URL
// MARK: -
let latitude: Double
let longitude: Double
}
The WeatherRequest struct is responsible for creating the URL instance for the request. We define a computed property, url of type URL, to access the URL for the request. The WeatherRequest struct uses string interpolation to append the coordinates to the value stored in baseUrl, returning the result.
import Foundation
struct WeatherRequest {
// MARK: - Properties
let baseUrl: URL
// MARK: -
let latitude: Double
let longitude: Double
// MARK: -
var url: URL {
return baseUrl.appendingPathComponent("\(latitude),\(longitude)")
}
}
We can now use the WeatherRequest struct to refactor the RootViewController class. Revisit RootViewController.swift and navigate to the fetchWeatherData() method.
private func fetchWeatherData() {
// Create Base URL
guard let baseUrl = URL(string: "https://api.darksky.net/forecast/") else {
return
}
// Append API Key
let authenticatedBaseUrl = baseUrl.appendingPathComponent("f574c275ab31a264ea69d4ffea0fde52")
// Initialize Weather Request
let weatherRequest = WeatherRequest(baseUrl: authenticatedBaseUrl, latitude: 37.335114, longitude: -122.008928)
// Create Data Task
URLSession.shared.dataTask(with: weatherRequest.url) { (data, response, error) in
if let error = error {
print("Request Did Fail (\(error)")
} else if let response = response {
print(response)
}
}.resume()
}
Instead of creating the URL instance manually, we initialize a WeatherRequest instance. We pass the authenticated base URL and the coordinates to the memberwise initializer of the WeatherRequest struct. We ask the WeatherRequest instance for the URL for the request by accessing its url property.
That's a good start, but we haven't removed the object literals from the fetchWeatherData() method yet. We could move them to the WeatherRequest struct, but that's something I want to avoid. The WeatherRequest struct shouldn't need to know about the Dark Sky API or the coordinates of the default location.
Namespacing
A pattern I'm very fond of is namespacing with enums. I'm sure you're already familiar with this pattern by now. Create a new group and name it Configuration. Add a new file to the Configuration group by choosing the Swift File template from the iOS section. Name the file Configuration.swift.


As the name implies, the purpose of this file is to group the configuration of the project. However, we don't want to turn it into a dump of random object literals. That's why we use enums to add order and structure.
Define an enum and name it WeatherService. We define a static constant property, apiKey, and assign the Dark Sky API key to it.
import Foundation
enum WeatherService {
static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
}
We also define a static constant property, baseUrl, that stores the base URL of the Dark Sky API.
import Foundation
enum WeatherService {
static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
static let baseUrl = URL(string: "https://api.darksky.net/forecast/")!
}
Notice that we append an exclamation mark to the initializer of the URL struct, forced unwrapping the result of the initialization. We append the exclamation mark for convenience and because the initialization of the URL instance should never fail. If it does, then we made a silly mistake we need to fix.
We define one more static property, a variable property named authenticatedBaseUrl. The property is of type URL and it combines the API key and the base URL to create, as the name implies, an authenticated base URL.
import Foundation
enum WeatherService {
static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
static let baseUrl = URL(string: "https://api.darksky.net/forecast/")!
static var authenticatedBaseUrl: URL {
return baseUrl.appendingPathComponent(apiKey)
}
}
Let's find out how the WeatherService enum can help us clean up the fetchWeatherData() method of the RootViewController class. Revisit RootViewController.swift and put the WeatherService enum to use in the initializer of the WeatherRequest struct.
private func fetchWeatherData() {
// Initialize Weather Request
let weatherRequest = WeatherRequest(baseUrl: WeatherService.authenticatedBaseUrl, latitude: 37.335114, longitude: -122.008928)
// Create Data Task
URLSession.shared.dataTask(with: weatherRequest.url) { (data, response, error) in
if let error = error {
print("Request Did Fail (\(error)")
} else if let response = response {
print(response)
}
}.resume()
}
This looks quite nice. We can make one more improvement, though. The apiKey and baseUrl properties of the WeatherService enum can be declared as private. There's no need to expose them to the rest of the application.
import Foundation
enum WeatherService {
private static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
private static let baseUrl = URL(string: "https://api.darksky.net/forecast/")!
static var authenticatedBaseUrl: URL {
return baseUrl.appendingPathComponent(apiKey)
}
}
Defaults
The coordinates of the default location shouldn't be exposed to the RootViewController class. There are several options to avoid this. A simple solution is creating another enum, Defaults, in Configuration.swift.
import Foundation
enum Defaults {
}
enum WeatherService {
private static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
private static let baseUrl = URL(string: "https://api.darksky.net/forecast/")!
static var authenticatedBaseUrl: URL {
return baseUrl.appendingPathComponent(apiKey)
}
}
The Defaults enum defines two static constant properties, latitude and longitude. Both properties are of type Double.
import Foundation
enum Defaults {
static let latitude: Double = 37.335114
static let longitude: Double = -122.008928
}
enum WeatherService {
private static let apiKey = "f574c275ab31a264ea69d4ffea0fde52"
private static let baseUrl = URL(string: "https://api.darksky.net/forecast/")!
static var authenticatedBaseUrl: URL {
return baseUrl.appendingPathComponent(apiKey)
}
}
We can update the fetchWeatherData() method of the RootViewController class by replacing the number literals with the static properties of the Defaults enum.
private func fetchWeatherData() {
// Initialize Weather Request
let weatherRequest = WeatherRequest(baseUrl: WeatherService.authenticatedBaseUrl, latitude: Defaults.latitude, longitude: Defaults.longitude)
// Create Data Task
URLSession.shared.dataTask(with: weatherRequest.url) { (data, response, error) in
if let error = error {
print("Request Did Fail (\(error)")
} else if let response = response {
print(response)
}
}.resume()
}
Core Location
Let's take it one step further by adding the Core Location framework to the mix. A location is defined by a latitude and a longitude. The CLLocation struct defined in the Core Location framework was designed for working with coordinates.
Revisit WeatherRequest.swift and add an import statement for the Core Location framework. Define a stored property location of type CLLocation.
import Foundation
import CoreLocation
struct WeatherRequest {
// MARK: - Properties
let baseUrl: URL
// MARK: -
let location: CLLocation
// MARK: -
let latitude: Double
let longitude: Double
// MARK: -
var url: URL {
return baseUrl.appendingPathComponent("\(latitude),\(longitude)")
}
}
We turn the stored properties latitude and longitude into computed properties. The latitude and longitude properties ask the location property for the components of the coordinate it manages. We mark both computed properties as private since they're only used internally by the WeatherRequest struct.
import Foundation
import CoreLocation
struct WeatherRequest {
// MARK: - Properties
let baseUrl: URL
// MARK: -
let location: CLLocation
// MARK: -
private var latitude: Double {
return location.coordinate.latitude
}
private var longitude: Double {
return location.coordinate.longitude
}
// MARK: -
var url: URL {
return baseUrl.appendingPathComponent("\(latitude),\(longitude)")
}
}
We also need to update the Defaults enum. Add an import statement for the Core Location framework and define a static constant property, location of type CLLocation. We can remove the latitude and longitude properties we defined earlier.
The last piece of the puzzle is updating the RootViewController class. We pass the default location to the new memberwise initializer of the WeatherRequest struct.
private func fetchWeatherData() {
// Initialize Weather Request
let weatherRequest = WeatherRequest(baseUrl: WeatherService.authenticatedBaseUrl, location: Defaults.location)
// Create Data Task
URLSession.shared.dataTask(with: weatherRequest.url) { (data, response, error) in
if let error = error {
print("Request Did Fail (\(error)")
} else if let response = response {
print(response)
}
}.resume()
}
I hope the benefits of these changes are clear. We no longer treat a location as a collection of numbers. Instead we treat a location as a set of coordinates. Later in this series, we continue to work with the Core Location framework.
What's Next?
I hope you agree that the minor changes we made in this episode have a significant impact on the implementation of the fetchWeatherData() method. The code looks cleaner, but we also accomplished something else. The RootViewController class no longer knows about the Dark Sky API. It's true that it accesses the WeatherService enum, but it isn't aware of the base URL and the API key.
In the next episode, we introduce the Model-View-ViewModel pattern. This means the RootViewController class will no longer be burdened with request management and response handling.