Networking was born out of the necessity of having a simple networking library that doesn’t have crazy programming abstractions or uses the latest reactive programming techniques, but just a plain, simple and convenient wrapper around NSURLSession that supports common needs such as faking requests and caching images out of the box. A library that is small enough to read in one go but useful enough to include in any project. That’s how Networking came to life, a fully tested library for iOS, tvOS, watchOS and OS X that will always be there for you.
Super friendly API
Singleton free
No external dependencies
Optimized for unit testing
Minimal implementation
Simple request cancellation
Fake requests easily (mocking/stubbing)
Runs synchronously in automatic testing environments (less XCTestExpectations)
Initializing an instance of Networking means you have to select a NSURLSessionConfiguration. The available types are Default, Ephemeral and Background, if you don’t provide any or don’t have special needs then Default will be used.
Default: The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain. It also stores cookies (by default) in the same shared cookie store as the NSURLConnection and NSURLDownload classes.
Ephemeral: An ephemeral session configuration object is similar to a default session configuration object except that the corresponding session object does not store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file. The main advantage to using ephemeral sessions is privacy. By not writing potentially sensitive data to disk, you make it less likely that the data will be intercepted and used later. For this reason, ephemeral sessions are ideal for private browsing modes in web browsers and other similar situations.
Background: This configuration type is suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.
// Default
let networking = Networking(baseURL: "http://httpbin.org")
// Ephemeral
let networking = Networking(baseURL: "http://httpbin.org", configuration: .ephemeral)
Changing request headers
You can set the headerFields in any networking object.
This will append (if not found) or overwrite (if found) what NSURLSession sends on each request.
networking.headerFields = ["User-Agent": "your new user agent"]
Authenticating
HTTP basic
To authenticate using basic authentication with a username “aladdin” and password “opensesame” you only need to do this:
let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(username: "aladdin", password: "opensesame")
networking.get("/basic-auth/aladdin/opensesame") { result in
// Successfully authenticated!
}
Bearer token
To authenticate using a bearer token“AAAFFAAAA3DAAAAAA” you only need to do this:
let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(token: "AAAFFAAAA3DAAAAAA")
networking.get("/get") { result in
// Successfully authenticated!
}
Custom authentication header
To authenticate using a custom authentication header, for example “Token token=AAAFFAAAA3DAAAAAA” you would need to set the following header field: Authorization: Token token=AAAFFAAAA3DAAAAAA. Luckily, Networking provides a simple way to do this:
let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerValue: "Token token=AAAFFAAAA3DAAAAAA")
networking.get("/get") { result in
// Successfully authenticated!
}
Providing the following authentication header Anonymous-Token: AAAFFAAAA3DAAAAAA is also possible:
let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerKey: "Anonymous-Token", headerValue: "AAAFFAAAA3DAAAAAA")
networking.get("/get") { result in
// Successfully authenticated!
}
Making a request
The basics
Making a request is as simple as just calling get, post, put, or delete.
GET example:
let networking = Networking(baseURL: "http://httpbin.org")
networking.get("/get") { result in
switch result {
case .success(let response):
let json = response.dictionaryBody
// Do something with JSON, you can also get arrayBody
case .failure(let response):
// Handle error
}
}
You can get the response headers inside the success.
let networking = Networking(baseURL: "http://httpbin.org")
networking.get("/get") { result in
switch result {
case .success(let response):
let headers = response.allHeaderFields
// Do something with headers
case .failure(let response):
// Handle error
}
}
By default all the requests are asynchronous, you can make an instance of Networking to do all its request as synchronous by using isSynchronous.
let networking = Networking(baseURL: "http://httpbin.org")
networking.isSynchronous = true
The Result type
If you aren’t familiar with the Result type, is what most networking libraries are using these days to deal with the awful amount of optional and unwrappings that we have to deal when doing networking. Before the Result type we had this problem:
// The old way
let networking = Networking(baseURL: "http://httpbin.org")
networking.get("/get") { json, headers, error in // Both are optional
if let error = error {
// OK, now we can handle the error
} else if let jsonArray = json as? [[String: Any]] {
// A valid JSON! Yay!
} else {
// Oh god, this shouldn't be happening, what do we do?!
}
}
Now, we don’t have to do it like that, leveraging in the Result type fixes this problem, the Result type is an enum that has two cases: success and failure. The success case has a response, the failure case has an error and a response, none of these ones are optionals, no more unwrapping!
Here’s how to use it:
// The best way
let networking = Networking(baseURL: "http://fakerecipes.com")
networking.get("/recipes") { result in
switch result {
case .success(let response):
// We know we'll be receiving an array with the best recipes, so we can just do:
let recipes = response.arrayBody // BOOM, no optionals. [[String: Any]]
// If we need headers or response status code we can use the HTTPURLResponse for this.
let headers = response.headers // [String: Any]
case .failure(let response):
// Non-optional error ✨
let errorCode = response.error.code
// Our backend developer told us that they will send a json with some
// additional information on why the request failed, this will be a dictionary.
let json = response.dictionaryBody // BOOM, no optionals here [String: Any]
// We want to know the headers of the failed response.
let headers = response.headers // [String: Any]
}
}
And that’s how we do things in Networking without optionals.
Choosing a Content or Parameter Type
The Content-Type HTTP specification is so unfriendly, you have to know the specifics of it before understanding that content type is really just the parameter type. Because of this Networking uses a ParameterType instead of a ContentType. Anyway, here’s hoping this makes it more human friendly.
JSON
Networking by default uses application/json as the Content-Type, if you’re sending JSON you don’t have to do anything. But if you want to send other types of parameters you can do it by providing the ParameterType attribute.
When sending JSON your parameters will be serialized to data using NSJSONSerialization.
let networking = Networking(baseURL: "http://httpbin.org")
networking.post("/post", parameters: ["name" : "jameson"]) { result in
// Successfull post using `application/json` as `Content-Type`
}
URL-encoding
If you want to use application/x-www-form-urlencoded just use the .formURLEncoded parameter type, internally Networking will format your parameters so they use Percent-encoding or URL-enconding.
let networking = Networking(baseURL: "http://httpbin.org")
networking.post("/post", parameterType: .formURLEncoded, parameters: ["name" : "jameson"]) { result in
// Successfull post using `application/x-www-form-urlencoded` as `Content-Type`
}
Multipart
Networking provides a simple model to use multipart/form-data. A multipart request consists in appending one or several FormDataPart items to a request. The simplest multipart request would look like this.
let networking = Networking(baseURL: "https://example.com")
let imageData = UIImagePNGRepresentation(imageToUpload)!
let part = FormDataPart(data: imageData, parameterName: "file", filename: "selfie.png")
networking.post("/image/upload", part: part) { result in
// Successfull upload using `multipart/form-data` as `Content-Type`
}
If you need to use several parts or append other parameters than aren’t files, you can do it like this:
let networking = Networking(baseURL: "https://example.com")
let part1 = FormDataPart(data: imageData1, parameterName: "file1", filename: "selfie1.png")
let part2 = FormDataPart(data: imageData2, parameterName: "file2", filename: "selfie2.png")
let parameters = ["username" : "3lvis"]
networking.post("/image/upload", parts: [part1, part2], parameters: parameters) { result in
// Do something
}
FormDataPart Content-Type:
FormDataPart uses FormDataPartType to generate the Content-Type for each part. The default FormDataPartType is .Data which adds the application/octet-stream to your part. If you want to use a Content-Type that is not available between the existing FormDataPartTypes, you can use .Custom("your-content-type).
Others
At the moment Networking supports four types of ParameterTypes out of the box: JSON, FormURLEncoded, MultipartFormData and Custom. Meanwhile JSON and FormURLEncoded serialize your parameters in some way, Custom(String) sends your parameters as plain NSData and sets the value inside Custom as the Content-Type.
For example:
let networking = Networking(baseURL: "http://httpbin.org")
networking.post("/upload", parameterType: .Custom("application/octet-stream"), parameters: imageData) { result in
// Successfull upload using `application/octet-stream` as `Content-Type`
}
Cancelling a request
Using path
Cancelling any request for a specific path is really simple. Beware that cancelling a request will cause the request to return with an error with status code URLError.cancelled.
let networking = Networking(baseURL: "http://httpbin.org")
networking.get("/get") { result in
// Cancelling a GET request returns an error with code URLError.cancelled which means cancelled request
}
networking.cancelGET("/get")
Using request identifier
Using cancelPOST("/upload") would cancel all POST request for the specific path, but in some cases this isn’t what we want. For example if you’re trying to upload two photos, but the user requests to cancel one of the uploads, using `cancelPOST(“/upload”) would cancell all the uploads, this is when ID based cancellation is useful.
let networking = Networking(baseURL: "http://httpbin.org")
// Start first upload
let firstRequestID = networking.post("/upload", parts: ...) { result in
//...
}
// Start second upload
let secondRequestID = networking.post("/upload", parts: ...) { result in
//...
}
// Cancel only the first upload
networking.cancel(firstRequestID)
Faking a request
Faking a request means that after calling this method on a specific path, any call to this resource, will return what you registered as a response. This technique is also known as mocking or stubbing.
Faking with successfull response:
let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: [["id" : 47333, "title" : "Site Design: Aquest"]])
networking.get("/stories") { result in
// JSON containing stories
}
Faking with contents of a file:
If your file is not located in the main bundle you have to specify using the bundle parameters, otherwise NSBundle.mainBundle() will be used.
let networking = Networking(baseURL: baseURL)
networking.fakeGET("/entries", fileName: "entries.json")
networking.get("/entries") { result in
// JSON with the contents of entries.json
}
Faking with status code:
If you do not provide a status code for this fake request, the default returned one will be 200 (SUCCESS), but if you do provide a status code that is not 2XX, then Networking will return an NSError containing the status code and a proper error description.
let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: nil, statusCode: 500)
networking.get("/stories") { result in
// error with status code 500
}
Downloading and caching an image
Downloading:
let networking = Networking(baseURL: "http://httpbin.org")
networking.downloadImage("/image/png") { result in
// Do something with the downloaded image
}
Cancelling:
let networking = Networking(baseURL: baseURL)
networking.downloadImage("/image/png") { result in
// Cancelling an image download returns an error with code URLError.cancelled which means cancelled request
}
networking.cancelImageDownload("/image/png")
Caching:
Networking uses a multi-cache architecture when downloading images, the first time the downloadImage method is called for a specific path, it will store the results in disk (Documents folder) and in memory (NSCache), so in the next call it will return the cached results without hitting the network.
let networking = Networking(baseURL: "http://httpbin.org")
networking.downloadImage("/image/png") { result in
// Image from network
networking.downloadImage("/image/png") { result in
// Image from cache
}
}
If you want to remove the downloaded image you can do it like this:
let networking = Networking(baseURL: "http://httpbin.org")
let destinationURL = try networking.destinationURL(for: "/image/png")
if let path = destinationURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
try! NSFileManager.defaultManager().removeItemAtPath(path)
}
Faking:
let networking = Networking(baseURL: baseURL)
let pigImage = UIImage(named: "pig.png")!
networking.fakeImageDownload("/image/png", image: pigImage)
networking.downloadImage("/image/png") { result in
// Here you'll get the provided pig.png image
}
Logging errors
Any error catched by Networking will be printed in your console. This is really convenient since you want to know why your networking call failed anyway.
Networking was born out of the necessity of having a simple networking library that doesn’t have crazy programming abstractions or uses the latest reactive programming techniques, but just a plain, simple and convenient wrapper around
NSURLSession
that supports common needs such as faking requests and caching images out of the box. A library that is small enough to read in one go but useful enough to include in any project. That’s how Networking came to life, a fully tested library for iOS, tvOS, watchOS and OS X that will always be there for you.Table of Contents
Choosing a configuration
Initializing an instance of Networking means you have to select a NSURLSessionConfiguration. The available types are
Default
,Ephemeral
andBackground
, if you don’t provide any or don’t have special needs thenDefault
will be used.Default
: The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain. It also stores cookies (by default) in the same shared cookie store as theNSURLConnection
andNSURLDownload
classes.Ephemeral
: An ephemeral session configuration object is similar to a default session configuration object except that the corresponding session object does not store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file. The main advantage to using ephemeral sessions is privacy. By not writing potentially sensitive data to disk, you make it less likely that the data will be intercepted and used later. For this reason, ephemeral sessions are ideal for private browsing modes in web browsers and other similar situations.Background
: This configuration type is suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.Changing request headers
You can set the
headerFields
in any networking object.This will append (if not found) or overwrite (if found) what NSURLSession sends on each request.
Authenticating
HTTP basic
To authenticate using basic authentication with a username “aladdin” and password “opensesame” you only need to do this:
Bearer token
To authenticate using a bearer token “AAAFFAAAA3DAAAAAA” you only need to do this:
Custom authentication header
To authenticate using a custom authentication header, for example “Token token=AAAFFAAAA3DAAAAAA” you would need to set the following header field:
Authorization: Token token=AAAFFAAAA3DAAAAAA
. Luckily, Networking provides a simple way to do this:Providing the following authentication header
Anonymous-Token: AAAFFAAAA3DAAAAAA
is also possible:Making a request
The basics
Making a request is as simple as just calling
get
,post
,put
, ordelete
.GET example:
POST example:
You can get the response headers inside the success.
By default all the requests are asynchronous, you can make an instance of Networking to do all its request as synchronous by using
isSynchronous
.The Result type
If you aren’t familiar with the Result type, is what most networking libraries are using these days to deal with the awful amount of optional and unwrappings that we have to deal when doing networking. Before the Result type we had this problem:
Now, we don’t have to do it like that, leveraging in the Result type fixes this problem, the Result type is an enum that has two cases:
success
andfailure
. Thesuccess
case has a response, thefailure
case has an error and a response, none of these ones are optionals, no more unwrapping!Here’s how to use it:
And that’s how we do things in Networking without optionals.
Choosing a Content or Parameter Type
The
Content-Type
HTTP specification is so unfriendly, you have to know the specifics of it before understanding that content type is really just the parameter type. Because of this Networking uses aParameterType
instead of aContentType
. Anyway, here’s hoping this makes it more human friendly.JSON
Networking by default uses
application/json
as theContent-Type
, if you’re sending JSON you don’t have to do anything. But if you want to send other types of parameters you can do it by providing theParameterType
attribute.When sending JSON your parameters will be serialized to data using
NSJSONSerialization
.URL-encoding
If you want to use
application/x-www-form-urlencoded
just use the.formURLEncoded
parameter type, internally Networking will format your parameters so they usePercent-encoding
orURL-enconding
.Multipart
Networking provides a simple model to use
multipart/form-data
. A multipart request consists in appending one or several FormDataPart items to a request. The simplest multipart request would look like this.If you need to use several parts or append other parameters than aren’t files, you can do it like this:
FormDataPart Content-Type:
FormDataPart
usesFormDataPartType
to generate theContent-Type
for each part. The defaultFormDataPartType
is.Data
which adds theapplication/octet-stream
to your part. If you want to use aContent-Type
that is not available between the existingFormDataPartType
s, you can use.Custom("your-content-type)
.Others
At the moment Networking supports four types of
ParameterType
s out of the box:JSON
,FormURLEncoded
,MultipartFormData
andCustom
. MeanwhileJSON
andFormURLEncoded
serialize your parameters in some way,Custom(String)
sends your parameters as plainNSData
and sets the value insideCustom
as theContent-Type
.For example:
Cancelling a request
Using path
Cancelling any request for a specific path is really simple. Beware that cancelling a request will cause the request to return with an error with status code URLError.cancelled.
Using request identifier
Using
cancelPOST("/upload")
would cancel all POST request for the specific path, but in some cases this isn’t what we want. For example if you’re trying to upload two photos, but the user requests to cancel one of the uploads, using `cancelPOST(“/upload”) would cancell all the uploads, this is when ID based cancellation is useful.Faking a request
Faking a request means that after calling this method on a specific path, any call to this resource, will return what you registered as a response. This technique is also known as mocking or stubbing.
Faking with successfull response:
Faking with contents of a file:
If your file is not located in the main bundle you have to specify using the bundle parameters, otherwise
NSBundle.mainBundle()
will be used.Faking with status code:
If you do not provide a status code for this fake request, the default returned one will be 200 (SUCCESS), but if you do provide a status code that is not 2XX, then Networking will return an NSError containing the status code and a proper error description.
Downloading and caching an image
Downloading:
Cancelling:
Caching:
Networking uses a multi-cache architecture when downloading images, the first time the
downloadImage
method is called for a specific path, it will store the results in disk (Documents folder) and in memory (NSCache), so in the next call it will return the cached results without hitting the network.If you want to remove the downloaded image you can do it like this:
Faking:
Logging errors
Any error catched by Networking will be printed in your console. This is really convenient since you want to know why your networking call failed anyway.
For example a cancelled request will print this:
A 404 request will print something like this:
To disable error logging use the flag
disableErrorLogging
.Installing
Networking is available through CocoaPods. To install it, simply add the following line to your Podfile:
Networking is also available through Carthage. To install it, simply add the following line to your Cartfile:
Author
This library was made with love by @3lvis.
License
Networking is available under the MIT license. See the LICENSE file for more info.
Attribution
The logo typeface comes thanks to Sanid Jusić.
Chinese description