23
RxAlamofire
Written by Florent Pillet
One of the basic needs of modern mobile applications is the ability to query remote resources. You’ve seen several examples of this throughout this book, using the basic extensions to NSURLSession
included with RxSwift.
Many developers like to use wrappers around OS-provided query mechanisms. The most popular, undoubtedly, is Alamofire https://github.com/Alamofire/Alamofire, a networking library with roots in Objective-C as it itself stems from AFNetworking https://github.com/AFNetworking/AFNetworking.
RxAlamofire (https://github.com/RxSwiftCommunity/RxAlamofire) is a project living under the RxSwiftCommunity organization. It adds an idiomatic Rx layer to Alamofire, making it straightforward to integrate into your observable workflow.
Most of the RxAlamofire API revolves around extending SessionManager
.
Basic requests
It’s straightforward to perform requests using the default SessionManager
session. If you don’t need to reuse a customized session, this can be your go-to request mechanism to retrieve a request result as raw text:
RxAlamofire.string(.get, stringURL)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
Most of the time you’ll want to deal with and decode JSON, as simply as this:
RxAlamofire.json(.get, stringURL)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
The resulting observable emits the result as a decoded JSON object. Since the element type is Any
, you’ll need to further map for observable chaining, or cast it in the subscription.
You can also obtain raw Data
:
RxAlamofire.data(.get, stringURL)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
RxAlamofire defines variants of these convenience functions prefixed with request
(requestString
, requestJSON
, requestData
), taking the same input parameters but returning an observable of a tuple of the HTTPURLResponse
object along with the decoded body.
Note: RxAlamofire requests are well-behaved observables. If you
dispose()
a subscription before the request has completed, the ongoing request is canceled. This is an important behavior of the framework, particularly when performing large uploads or downloads.
All of the above are convenience functions using the default SessionManager
. Under the hood, they call the actual implementation defined as reactive extensions to SessionManager
:
let session = SessionManager.default
session.rx.json(.get, stringURL)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
Note: The
SessionManager
reactive extensions returning observables of tuple are prefixed withresponse
, not withrequest
. For example, you’ll usesession.rx.responseJSON(.get, stringURL)
to obtain anObservable<(HTTPURLResponse, Any)>
.
Request customization
The examples above didn’t modify the default values for customized parameters, URL encoding and HTTP headers. But that’s easy as well:
// get current weather in london
RxAlamofire.json(.get,
"http://api.openweathermap.org/data/2.5/weather",
parameters: ["q": "London", "APPID": "{APIKEY}"])
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
Response validation
The request
and session.rx.request
APIs let you perform further validation and manipulation by processing the underlying DataRequest
. RxAlamofire provides convenience extensions to do this easily:
let response = request(.get, stringURL)
.validate(statusCode: 200 ..< 300)
.validate(contentType: ["text/json"])
.json()
Downloading files
You can download files to a destination determined by AlamoFire’s DownloadDestination
closure type:
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let docsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filename = response.suggestedFilename ?? "image.png"
let fileURL = docsURL.appendingPathComponent(filename)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
RxAlamofire.download(URLRequest(url: someURL), to: destination)
.subscribe(onCompleted: { print("Download complete") })
.disposed(by: disposeBag)
Upload tasks
Uploading is equally simple. You can upload in-memory Data
, files or provide an InputStream
as the data source:
RxAlamofire.upload(someData, to: URLRequest(url: someURL))
.subscribe(onCompleted: { print("Upload complete") })
.disposed(by: disposeBag)
Tracking progress
Track upload and download progress by extracting an Observable<RxProgress>
from the AlamoFire.Request
object emitted by request, download and upload APIs:
RxAlamofire.upload(localFileURL,
to: URLRequest(url: remoteURL))
.validate() // check acceptable status codes
.progress()
.subscribe (
onNext: { progress in
let percent = Int(100.0 * progress.completed)
print("Upload progress: \(percent)%")
},
onCompleted: { print("Upload complete") }
)
.disposed(by: disposeBag)