Forest client is a flexible and extensible RESTful API client framework built on top of URLSession and URLSessionTask. It already includes network object mappers from JSON to the most commonly used data types. Because of its simple data encoding/decoding approach and extensible architecture you can easily add your custom network object mappers. Forest provides all of the features needed to build robust client for your backend services.
You could ask why not to get any other proven networking framework? Sure, you’ll get one best suitable for your needs and style preference, but it's always good to have an options. Following is the list of features I wanted from higher level networking layer and implemented in Forest client:
Decodable protocolpod 'Forest'Add Protobufs supporting extensions:
pod 'Forest/Protobuf'To use reachability service:
pod 'Forest/Reachability'And don't forget to import the framework:
import ForestJust put the files from Core and Protobuf directories somethere in your project. To use Protobuf extensions you need additionally integrate SwiftProtobuf framework into your project.
The core class which handles network task is ServiceTask. ServiceTask includes factory methods helping to configure request and response params and handlers. If you need more control over the process of making request and handling the response, you can use delegation and implement ServiceTaskRetrofitting protocol and modify task behavior via retrofitter. Also you can subclass ServiceTask, it is built for that.
ServiceTask()
.url("https://host.com/path/to/endpoint")
.method(.GET)
.query(["param": value])
// Expecting valid JSON response
.json { (object, response) in
print("JSON response received: (object)")
}
.error { (error, response) in
print("Error occurred: (error)")
}
.perform()Send and receive data using objects conforming to Codable protocol:
struct NameRequest: Encodable {
let name: String
}
struct NameResponse: Decodable {
let isValid: Bool
}
ServiceTask()
// Set base url and HTTP method
.endpoint(.POST, "https://host.com")
// Add path to resource
.path("/path/to/resource")
// Serialize our Codable struct and set body
.body(codable: NameRequest("some"))
// Expect response with the object of 'NameResponse' type
.codable { (object: NameResponse, response) in
print("Name valid: (object.isValid)")
}
// Otherwise will fail with error
.error { (error, response) in
print("Error occured: (error)")
}
.perform()Just download some file:
ServiceTask()
.headers(["Authorization": "Bearer (token)"])
.method(.PUT)
.url("https://host.com/file/12345")
.body(text: "123456789")
.file { (url, response) in
print("Downloaded: (url)")
// Remove temp file
try? FileManager.default.removeItem(at: url)
}
.error { (error, response) in
print("Error occured: (error)")
}
// When download destination not provided, content will be downloaded and saved to temp file
.download()Upload multipart form data encoded content:
do {
// Create new form data builder
var formDataBuilder = FormDataBuilder()
// Filename and MIME type will be obtained automatically from URL. It can be provided explicitly too
formDataBuilder.append(.file(name: "image", url: *url*))
// Generate form data in memory. It also can be written directly to disk or stream using encode(to:) method
let formData = try formDataBuilder.encode()
ServiceTask()
.endpoint(.POST, "https://host.com/upload")
.body(data: formData, contentType: formDataBuilder.contentType)
.response(content: { (response) in
switch response {
case .success:
print("Done!")
case .failure(let error):
print("Failed to upload: (error)")
}
})
.perform()
}
catch {
print("(error))
return
}
Send and receive Protobuf messages (gRPC over HTTP):
ServiceTask()
.endpoint(.POST, "https://host.com")
// Create and configure request message in place
.body { (message: inout Google_Protobuf_StringValue) in
message.value = "something"
}
// Expecting Google_Protobuf_Empty message response
.proto{ (message: Google_Protobuf_Empty, response) in
print("Done!")
}
.error { (error, response) in
print("Error occured: (error)")
}
.perform()
// Or another version of the code above with explicitly provided types
ServiceTask()
.endpoint(.POST, "https://host.com")
// Create and configure request message in place
.body(proto: Google_Protobuf_SourceContext.self) { (message) in
message.fileName = "file.name"
}
// Expecting Google_Protobuf_Empty message response
.response(proto: Google_Protobuf_Empty.self) { (response) in
switch response {
case .success(let message):
print("Done!")
case .failure(let error):
print("Error occured: (error)")
}
}
.perform()Natan Zalkin [email protected]
Forest is available under the MIT license. See the LICENSE file for more info.