Building the URL

Communication with servers is done via requests. A request encapsulates information about the interaction between the application and the server, and its most important piece of information is the destination URL.

In this section, you will build up the URL for retrieving interesting photos from the Flickr web service. The architecture of the application will reflect best practices. For example, each type that you create will encapsulate a single responsibility. This will make your types robust and flexible and your application easier to reason about. To be a good iOS developer, you not only need to get the job done, but you also need to get it done thoughtfully and with foresight.

Formatting URLs and requests

The format of a web service request varies depending on the server that the request is reaching out to. There are no set-in-stone rules when it comes to web services. You will need to find the documentation for the web service to know how to format a request. As long as a client application sends the server what it wants, you have a working exchange.

Flickr’s interesting photos web service wants a URL that looks like this:

https://api.flickr.com/services/rest/?method=flickr.interestingness.getList
&api_key=a6d819499131071f158fd740860a5a88&extras=url_h,date_taken
&format=json&nojsoncallback=1

Web service requests come in all sorts of formats, depending on what the creator of that web service is trying to accomplish. The interesting photos web service, where pieces of information are broken up into key-value pairs, is pretty common.

The key-value pairs that are supplied as part of the URL are called query items. Each of the query items for the interesting photos request is defined by and is unique to the Flickr API.

  • The method determines which endpoint you want to hit on the Flickr API. For the interesting photos, this is the string "flickr.interestingness.getList".

  • The api_key is a key that Flickr generates to authorize an application to use the Flickr API.

  • The extras are attributes passed in to customize the response. Here, the url_h,date_taken value tells the Flickr server that you want the photo URLs to also come back in the response along with the date the photo was taken.

  • The format item specifies that you want the payload coming back to be JSON.

  • The nojsoncallback item specifies that you want JSON back in its raw format.

URLComponents

You will create two types to deal with all of the web service information. The FlickrAPI struct will be responsible for knowing and handling all Flickr-related information. This includes knowing how to generate the URLs that the Flickr API expects as well as knowing the format of the incoming JSON and how to parse that JSON into the relevant model objects. The PhotoStore class will handle the actual web service calls. Let’s start by creating the FlickrAPI struct.

Create a new Swift file named FlickrAPI and declare the FlickrAPI struct, which will contain all of the knowledge that is specific to the Flickr API.

import Foundation

struct FlickrAPI {

}

You are going to use an enumeration to specify which endpoint on the Flickr server to hit. For this application, you will only be working with the endpoint to get interesting photos. However, Flickr supports many additional APIs, such as searching for images based on a string. Using an enum now will make it easier to add endpoints in the future.

In FlickrAPI.swift, create the Method enumeration. Each case of Method has a raw value that matches the corresponding Flickr endpoint.

import Foundation

enum Method: String {
    case interestingPhotos = "flickr.interestingness.getList"
}

struct FlickrAPI {

}

In Chapter 2, you learned that enumerations can have raw values associated with them. Although the raw values are often Ints, you can see here a great use of String as the raw value for the Method enumeration.

Now declare a type-level property to reference the base URL string for the web service requests.

enum Method: String {
    case interestingPhotos = "flickr.interestingness.getList"
}

struct FlickrAPI {

    static let baseURLString = "https://api.flickr.com/services/rest"
}

A type-level property (or method) is one that is accessed on the type itself – in this case, the FlickrAPI type. For structs, type properties and methods are declared with the static keyword; classes use the class keyword. You used a type method on the UIView class in Chapter 8 when you called the animate(withDuration:animations:) method. You also used a type method on UIImagePickerController in Chapter 15 when you called the isSourceTypeAvailable(_:) method. Here, you are declaring a type-level property on FlickrAPI.

The baseURLString is an implementation detail of the FlickrAPI type, and no other type needs to know about it. Instead, they will ask for a completed URL from FlickrAPI. To keep other files from being able to access baseURLString, mark the property as private.

struct FlickrAPI {

    private static let baseURLString = "https://api.flickr.com/services/rest"
}

This is called access control. You can control what can access the properties and methods on your own types. There are five levels of access control that can be applied to types, properties, and methods:

  • open – This is used only for classes, and mostly by framework or third-party library authors. Anything can access this class, property, or method. Additionally, classes marked as open can be subclassed and methods can be overridden outside of the module.

  • public – This is very similar to open; however, classes can only be subclassed and methods can only be overridden inside (not outside of) the module.

  • internal – This is the default. Anything in the current module can access this type, property, or method. For an app, only files within your project can access these. If you write a third-party library, then only files within that third-party library can access them – apps that use your third-party library cannot.

  • fileprivate – Anything in the same source file can see this type, property, or method.

  • private – Anything within the enclosing scope can access this type, property, or method.

Now you are going to create a type method that builds up the Flickr URL for a specific endpoint. This method will accept two arguments: The first will specify which endpoint to hit using the Method enumeration, and the second will be an optional dictionary of query item parameters associated with the request.

Implement this method in your FlickrAPI struct in FlickrAPI.swift. For now, this method will return an empty URL.

private static func flickrURL(method: Method,
                              parameters: [String:String]?) -> URL {

    return URL(string: "")!
}

Notice that the flickrURL(method:parameters:) method is private. It is an implementation detail of the FlickrAPI struct. An internal type method will be exposed to the rest of the project for each of the specific endpoint URLs (currently, just the interesting photos endpoint). These internal type methods will call through to the flickrURL(method:parameters:) method.

In FlickrAPI.swift, define and implement the interestingPhotosURL computed property.

static var interestingPhotosURL: URL {
    return flickrURL(method: .interestingPhotos,
                     parameters: ["extras": "url_h,date_taken"])
}

Time to construct the full URL. You have the base URL defined as a constant, and the query items are being passed into the flickrURL(method:parameters:) method via the parameters argument. You will build up the URL using the URLComponents class, which is designed to take in these various components and construct a URL from them.

Update the flickrURL(method:parameters:) method to construct an instance of URLComponents from the base URL. Then, loop over the incoming parameters and create the associated URLQueryItem instances.

private static func flickrURL(method: Method,
                              parameters: [String:String]?) -> URL {

    return URL(string: "")!

    var components = URLComponents(string: baseURLString)!

    var queryItems = [URLQueryItem]()

    if let additionalParams = parameters {
        for (key, value) in additionalParams {
            let item = URLQueryItem(name: key, value: value)
            queryItems.append(item)
        }
    }
    components.queryItems = queryItems

    return components.url!
}

The last step in setting up the URL is to pass in the parameters that are common to all requests: method, api_key, format, and nojsoncallback.

The API key is a token generated by Flickr to identify your application and authenticate it with the web service. We have generated an API key for this application by creating a Flickr account and registering this application. (If you would like your own API key, you will need to register an application at www.flickr.com/​services/​apps/​create.)

In FlickrAPI.swift, create a constant that references this token.

struct FlickrAPI {

    private static let baseURLString = "https://api.flickr.com/services/rest"
    private static let apiKey = "a6d819499131071f158fd740860a5a88"

Double-check to make sure you have typed in the API key exactly as presented here. It has to match or the server will reject your requests. If your API key is not working or if you have any problems with the requests, check out the forums at forums.bignerdranch.com for help.

Finish implementing flickrURL(method:parameters:) to add the common query items to the URLComponents.

private static func flickrURL(method: Method,
                              parameters: [String:String]?) -> URL {

    var components = URLComponents(string: baseURLString)!

    var queryItems = [URLQueryItem]()

    let baseParams = [
        "method": method.rawValue,
        "format": "json",
        "nojsoncallback": "1",
        "api_key": apiKey
    ]

    for (key, value) in baseParams {
        let item = URLQueryItem(name: key, value: value)
        queryItems.append(item)
    }

    if let additionalParams = parameters {
        for (key, value) in additionalParams {
            let item = URLQueryItem(name: key, value: value)
            queryItems.append(item)
        }
    }
    components.queryItems = queryItems

    return components.url!
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset