Skip to content

mobven/MBNetworking

Repository files navigation

Networking

Networking is a design-pattern framework based on Apple's URLSession. It's evolved from the idea of replacing Alamofire (in some projects also Moya) with native URLSession functions.

Dependency Graph draw.io

Networking Dependency Graph

Requirements

  • iOS 10.0+
  • Xcode 12+
  • Swift 5.3+

Installation

Swift Package Manager

Framework is supported only via Swift Package Manager and is not planned to be distributed otherwise, unless there is a specific requirement. As Swift Package Manager is the first official distribution tool managed by Apple for Swift, it's stable currently and there is an active development on it which promises it will be used for a while.

Project can be installed using SPM with its github URL.

https://github.com/mobven/Networking

Usage

Networkable functionalities can be achieved creating enums which conform to Networkable protocol and overriding its request parameter. Later, when fetch is called, Networkable uses this request to proceed the request.

Creating Networkables

import MBNetworking
enum API {
    enum Login: Networkable {
        /// Login with username and password with GET request.
        case loginGet(username: String, password: String)
        /// Login with username and password with POST request.
        case loginPost(request: LoginRequest)
        
        var request: URLRequest {
            switch self {
            case let .loginGet(username, password):
                return getRequest(url: API.getURL(endpoint: "LoginPOST"),
                                  queryItems: ["username": username, "password": password])
            case let .loginPost(request):
                return getRequest(url: API.getURL(endpoint: "LoginGET"),
                                  encodable: request)
            }
        }
    }
    
    private static func getURL(endpoint: String) -> URL {
        let baseEndpoint = "https://example.com/api/"
        return URL(forceString: "\(baseEndpoint)\(endpoint)")
    }
}
struct LoginRequest: Encodable {
    var username: String
    var password: String
}

Fetching data from Networkables

API.Login.loginGet(username: "admin", password: "admin").fetch(LoginResponse.self) { result in
    switch result {
    case let .success(response):
        print("Succeeded with \(response)")
    case let .failure(error):
        print("Failed with \(error)")
    }
}
API.Login.loginPost(request: LoginRequest(username: "admin", password: "admin")).fetch(LoginResponse.self) { result in
    switch result {
    case let .success(response):
        print("Succeeded with \(response)")
    case let .failure(error):
        print("Failed with \(error)")
    }
}
struct LoginResponse: Decodable {
    var name: String?
    var surname: String?
}

Advanced usage

Timeouts

By default, request timeouts are 60 seconds for request and resource which are relatively equivalent to URLSessionConfiguration's timeoutIntervalForRequest and timeoutIntervalForResource parameters. Currently Networking supports timeouts to be set globally, so once changed new value will be applied to all next requests. Having different timeout for each single request is planned to be supported in the future to enable Networkable include timeout parameter.

NetworkableConfigs.default.setTimeout(for: 30, resource: 30)

SSL Pinning

Networking, like setting timeout, supports only global certificates set via:

if let path = Bundle.main.path(forResource: "certificate", ofType: "der") {
    NetworkableConfigs.default.setCertificatePaths(path)
}

Enable server-trusted authentication.

Networking can be configured to continue with any challenge, in case your server has no publicly trusted certifiace. Apple may reject your application, for this usage. It's on your own responsibility

NetworkableConfigs.default.setServerTrustedURLAuthenticationChallenge()

NetworkLogMonitoringDelegate

Implement this protocol to handle tasks creation, data receiving, and task completion with error.

  1. Set monitoring delegate. Networking can pass data, tasks and errors of URLSession by NetworkLogMonitoringDelegate methods.
NetworkableConfigs.default.set(networkLogMonitoringDelegate: NetworkLogMonitoringDelegate)
  1. Implement the NetworkLogMonitoringDelegate protocol to your class.
class YourClass: NetworkLogMonitoringDelegate {
// Implement protocol methods
        
///Called when a URLSessionTask is created.
func logTaskCreated(task: URLSessionTask) { /* Handle task creation */ }

///Called when a URLSessionDataTask receives data.
func logDataTask(dataTask: URLSessionDataTask, didReceive data: Data) { /* Handle data receive */ }

//Called when a URLSessionTask completes with error.
func logTask(task: URLSessionTask, didCompleteWithError error: Error) { /* Handle task completion with error */ }
}

File Upload

Networking supports file upload through uploadRequest using MBNetworking.Files.

extension API {
    enum File: Networkable {
        /// Multipart file upload request.
        case upload(parameters: [String: String], files: [MBNetworking.File])
        
        var request: URLRequest {
            switch self {
            case let .upload(parameters, files):
                return uploadRequest(url: API.getURL(endpoint: "FileUpload"),
                                     parameters: parameters, files: files)
            }
        }
    }
}

Data Download

You can use Data type as decodable to download Data from service. This can be helpful for downloading image, html data.

extension API {
    enum Download: Networkable {
        case image(url: URL)
        
        var request: URLRequest {
            switch self {
            case let .image(url):
                return getRequest(url: url, queryItems: [:])
            }
        }
    }
}

Unit Testing

StubURLProtocol (Deprecated)

⚠️ This method has some timing issues on unit testing, depending on the running machine's performance.

Networking provides StubURLProtocol which acts like man-in-the-middle to simplify stub data for unit testing. You can achieve this by setting StubURLProtocol.result to a any of the available values which are:

  • success(Data): Successfull result with specified data
  • failure(Error): Failure with the specified Error.
  • case failureStatusCode(Int): Failure with the specified status code.

There're helper functions to get the StubURLProtocol.Result with the data from specified bundle resource.

StubURLProtocol.result = .getData(from: Bundle.module.url(forResource: "some", withExtension: "txt"))
StubURLProtocol.result = .getData(from: Bundle.module.path(forResource: "some", ofType: "txt"))

You can define a delay in seconds for reading stub data:

StubURLProtocol.delay = 3

StubURLProtocol is designed for unit tests only and would not work if there's no any unit testing process in progress.

You need to set StubURLProtocol.result before each Networkable.fetch call to achieve necessary result.

Task Cancellation

Networking supports cancelling ongoing requests. It can be helpful when you need to cancel all fired requests without waiting to respond them.

NetworkableTasks.cancelAll()

After calling cancelAll() function, ongoing request returns NetworkingError.dataTaskCancelled in their failure block immediately.

Setting URLSessionConfiguration

Networking allows to set URLSessionConfiguration through its NetworkableConfigs. Possible values can be default and ephemeral. By default, MBNetworking uses URLSessionConfiguration.default.

NetworkableConfigs.default.set(configuration: .ephemeral)

Setting Custom URLSessionDelegate as PinnableSessionDelegate

Networking supports custom URLSessionDelegate as PinnableSessionDelegate.

  • parameter challenge: PinnableSessionDelegate: The PinnableSessionDelegate protocol conforms to the URLSessionDelegate protocol.
public func setServerTrustedAuthenticationChallenge(_ challenge: PinnableSessionDelegate) 

What's next

  • Async/await support with Swift 5.5