Latest 0.2.0
Homepage https://github.com/jinbass/NJNetworking
License MIT
Platforms ios 8.0
Authors

NJNetworking is a lightweight wrapper of URLSession which normalize the definition of APIs across your application. The concept has been tutorialed in a lot of websites/blogs so actually no big deal here. On top of that, NJNetworking provides a simple approach of loading local json files for a server-less development of client app, as well as notifications that are sent when you send/receive server API request/response.

Features

Normalizing API definition

In an application that communicates a lot with servers, it’s important while sometimes tricky to maintain an API list so you could quickly lookup what an API is used for, its URL, its parameters, its response, etc. A common approach is to define a protocol that every API should follow to explain itself.
In NJNetworking, an API protocol has following metadatas.

public protocol API {
    var method: HTTPMethod { get }
    var path: String { get set }
    var parameters: Any { get }
    var headers: [String: String] { get }
    var baseURL: String { get }
    var contentType: ContentType { get }
    var requireBearerToken: BearerTokenRequirement { get }
    var requireBasicAuthentication: Bool { get }
    var timeoutInterval: Double { get }
    var successResponseMockFileName: String { get }
    var failureResponseMockFileName: String { get }
    associatedtype SuccessResponseType: Decodable
    associatedtype FailureResponseType: Decodable
}

Everything except the last 4 should be really easy to understand. "MockFileName"s are the local json file names that you’d like to mock instead of firing a real networking call(will explain later), while associatedtypes are the response data format when an API returns.

For example, for an API that fetches weather information, we could define it as following:

public struct WeatherAPI: API {
    public var method: HTTPMethod = .get
    public var path: String = "/api/location/search/"
    public var baseURL: String = "https://www.metaweather.com"
    public var parameters: Any
    public typealias SuccessResponseType = [Weather]
    public typealias FailureResponseType = APIErrorResponse
    public var successResponseMockFileName: String = "weathers_success"
    public var failureResponseMockFileName: String = "weathers_failure"
    public init(query: String) {
        parameters = ["query": query]
    }
}

You can use class as well if you need to share some common parameters like baseURL by inheritance. Make sure you setup the parameters map in initializer if necessary, the construction of full url or body depending on the HTTP method will be handled for you by APIClient(which you don’t need to care much about).

To use an API, all you need to do is to feed the API object to a APIService class(or its subclass).

let weatherAPI = WeatherAPI(query: "tokyo")
let service = APIService(configuration: APIService.Configuration(apiClient: RealAPIClient()))
service.sendRequest(api: api) { 
    result in
    // parse your result here
}

// Or, you can wrap above code to group a series of related APIs
public class WeatherService: APIService {
    @discardableResult
    public func getWeathers(query: String, completion: @escaping ([Weather])->()) -> URLSessionTask? {
        let api = WeatherAPI(query: query)
        return sendRequest(api: api) { result in
            completion(APIService.initialValue(result))
        }
    }
}

When creating APIService, it requires an object of APIService.Configuration, which contains an APIClient(explain later) and headers you want to pass to the API.
Note: For static headers you could specify it inside the API protocol and initialize in the constructor. Headers specified in APIService.Configuration are those that are basically added to EVERY request.

Load local JSON file without changing caller’s I/F

A common needs for web API based applicatin is to using mocks without firing a real networking quest. It is not difficult to achieve by using several if-statements that diverge your code path but it could be annoying. Specifically, you may need to change I/F on caller side because loading a local file is a synchronous operation while networking calls are asynchronous.

NJNetworking tries to solve this problem by differenciating real/mock calls with the type of APIClient that gets passed into an APIService. Technically APIClient is just an implementation of APIRequestSendable protocol, which just basically receives an API object and return some data.
NJNetworking has prepared 2 kinds of APIClient which you can use: RealAPIClient and MockAPIClient. RealAPIClient will fire a real networking transaction while MockAPIClient will load the local json file specified in the API definition.
A typical usage might be holding a global scoped APIService and switch its client according to your needs.

// API configuration, defined somewhere in a singleton for example
var apiServiceConfiguration: APIService.Configuration {
    var client: APIRequestSendable = RealAPIClient()
    #if !RELEASE
    if isMockAPIClient {
        client = MockAPIClient()
    }
    #endif
    return APIService.Configuration(apiClient: client,
                                    headers: dynamicHTTPHeaders)
}

// Some where in your view controller
let service = APIService(configuration: AppSession.shared.apiServiceConfiguration)

This way you don’t need to change any code in view controller in order to switch loading from network/mock.

Track specific transaction

Another common scenario in API based app is sometimes we want to know if something(mostly bug) was caused by API data, parsing or business logcis. It would be helful if we could know what data gets parsed in the transaction we just fired.
NJNetworking provides a simple tracking feature by utilizing the standard NSNotification system. Just register any object to receive request-sending/response-receiving notifications so you can get notified for all transactions that are fired via APIService.

public init() {
    NotificationCenter.default.addObserver(self, selector: #selector(didSendRequest), name: NSNotification.Name(rawValue: APIService.APIServiceConstant.Notification.didSendRequest), object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(didReceiveResponse), name: NSNotification.Name(rawValue: APIService.APIServiceConstant.Notification.didReceiveRequest), object: nil)
}

@objc private func didSendRequest(notification: Notification) {
    guard let userInfo = notification.userInfo as? [String: String] else {
        fatalError()
    }
    let method      = userInfo[APIService.APIServiceConstant.UserInfoKey.method]!
    let path        = userInfo[APIService.APIServiceConstant.UserInfoKey.path]!
    let identifier  = userInfo[APIService.APIServiceConstant.UserInfoKey.identifier]!
    print("Did sent request (identifier): (method) (path)")
}

@objc private func didReceiveResponse(notification: Notification) {
    guard let userInfo = notification.userInfo as? [String: String] else {
        fatalError()
    }
    let response    = userInfo[APIService.APIServiceConstant.UserInfoKey.response]!
    let identifier  = userInfo[APIService.APIServiceConstant.UserInfoKey.identifier]!
    print("Did receive response (identifier): (response)")
}

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

Installation

NJNetworking is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'NJNetworking'

Author

Jin Nagumo

License

NJNetworking is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "NJNetworking",
    "version": "0.2.0",
    "summary": "A thin wrapper of URLSession",
    "description": "NJNetworking is a thin wrapper of URLSession to achieve simple tasks while keeping API definitions easy to navigate and manage.",
    "homepage": "https://github.com/jinbass/NJNetworking",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "jinbass": "[email protected]"
    },
    "source": {
        "git": "https://github.com/jinbass/NJNetworking.git",
        "tag": "0.2.0"
    },
    "platforms": {
        "ios": "8.0"
    },
    "swift_version": "5.0",
    "source_files": "NJNetworking/Classes/**/*"
}

Pin It on Pinterest

Share This