Type-safe networking abstraction layer that associates request type with response type.

Last update: Jun 14, 2022

APIKit

Build Status codecov Carthage compatible Version Platform Swift Package Manager

APIKit is a type-safe networking abstraction layer that associates request type with response type.

// SearchRepositoriesRequest conforms to Request protocol.
let request = SearchRepositoriesRequest(query: "swift")

// Session receives an instance of a type that conforms to Request.
Session.send(request) { result in
    switch result {
    case .success(let response):
        // Type of `response` is `[Repository]`,
        // which is inferred from `SearchRepositoriesRequest`.
        print(response)

    case .failure(let error):
        self.printError(error)
    }
}

Requirements

  • Swift 5.0 or later
  • iOS 8.0 or later
  • Mac OS 10.10 or later
  • watchOS 2.0 or later
  • tvOS 9.0 or later

If you use Swift 2.2 or 2.3, try APIKit 2.0.5.

If you use Swift 4.2 or before, try APIKit 4.1.0.

Installation

Carthage

  • Insert github "ishkawa/APIKit" ~> 5.0 to your Cartfile.
  • Run carthage update.
  • Link your app with APIKit.framework in Carthage/Build.

CocoaPods

  • Insert pod 'APIKit', '~> 5.0' to your Podfile.
  • Run pod install.

Note: CocoaPods 1.4.0 is required to install APIKit 5.

Documentation

Advanced Guides

Migration Guides

GitHub

https://github.com/ishkawa/APIKit
Comments
  • 1. Redesign for Swift 2

    Swift 2 is coming! I think redesigning APIKit for Swift 2 is necessary to take full advantage of it.

    • protocol extension makes Reuqest protocol more flexilble.
    • ErrorType is useful to express errors in APIKit.
    • do-try-catch introduced easier error handling model (if process is synchronous).

    Here are tasks:

    • [x] Move default configuration of request to extension of Request.
    • [x] Detailed and comprehensive errors using ErrorType.
    • [x] Adopt do-try-catch in synchronous error handling.

    Any comments are welcome.

    Reviewed by ishkawa at 2015-06-11 10:08
  • 2. [WIP] Add mechanism to intercept requests

    This PR implements a mechanism to intercept each requests for following purposes:

    • Insert common operations before/after each requests.
    • Manage network indicator visibility.

    An interceptor type looks like below:

    class MyRequestInterceptor: RequestInterceptorType {
        func interceptBeforeRequest<T: RequestType>(request: T) {
            /* To be called before each requests */
        }
    
        func interceptAfterRequest<T: RequestType>(request: T, result: Result<T.Response, APIError>) {
            /* To be called after each requests */
        }
    }
    

    Then, append its instance to requestInterceptors of an instance of Session:

    let session = /* Get the session to add interceptors. */
    session.requestInterceptors.append(interceptor)
    
    Reviewed by ishkawa at 2015-10-17 16:57
  • 3. Provide instance to give a change to implement delegate methods of NSURLSession

    Users of APIKit may want to implement delegate methods of NSURLSession to hook events or to handle authentication challenge, so I think we have to provide instances for each subclasses of API and re-implement wrapper of NSURLSession using delegate methods (not block). Currently, an instance of API subclass is provided as a singleton because I would like to keep interface simple and multiple clients are not necessary for one web API.

    @ikesyo how do you think?

    Reviewed by ishkawa at 2015-02-23 15:40
  • 4. Question about custom interception/retry functionality

    Hey there,

    I have a specific requirement for my app that I'm working on. The API web service uses JSON Web Tokens. It requires me to pass in a valid x-session-token header for each protected API endpoint.

    These session tokens expire every 24h, which means I need to reauthenticate and retry my existing request. Is that currently possible with APIKit? I've seen some PRs about interceptors for version 2.0 but I'm not sure if that's what I need.

    Here's an example of what I mean: Let's say I need to call the GET /user endpoint of my API and it requires me to pass in a valid x-session-token in the header. If the token has expired, I will get an error response, something like this:

    success: false, error: {
        code = E1100;
        hint = Token;
        message = "Session token has expired.";
    }
    

    Now whenever I get an error response with the error.code == "E1100", what I want to do is, automatically call the POST /authenticate_session endpoint, get the new session token and automatically retry the GET /user request with the updated x-session-token header.

    Can you point me in the right direction of how I could best implement this with APIKit?

    I'm currently using a different API library for my app and it's proving to be a pain (I'm literally at the point of where I'm trying to fight and go against how the framework was intended to work). Before I start implementing my API logic from scratch, I'm exploring my options and would like to research if I could accomplish this functionality requirement using APIKit (or any other open source library).

    Thanks for your help.

    Reviewed by jyounus at 2016-04-30 12:31
  • 5. Unable to send [Int] value as a parameter

    以下のようなリクエストを送っています。 idsというkeyでInt型のArrayを送信したいのですが、パラメータが["(\n 672,\n 669\n)"]こういった形になってしまします。 やり方が悪いのでしょうか...? 何卒よろしくお願いします。

    struct deleteImage: UserImageRequestType {
                typealias Response = Bool
    
                let ids:        [Int]
                let method    = HTTPMethod.DELETE
                var path:       String { return "/v1/user_images/delete" }
                var parameters: [String: AnyObject] { return ["ids": ids] }
    
                func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response? {
                     ...
            }
    }
    
    Reviewed by yukitoto at 2016-07-21 11:15
  • 6. Carthage issue

    Hi, When i use APIKit with Carthage i get some errors, this is my Cartfile github "ishkawa/APIKit" ~> 1.0

    first issue is that it checks out as Checking out APIKit at "1.0.0-beta5" and i think the problem is that you changed beta5 to point style (beta.6) so it always checks out at beta5 and the second issue is that i get this error: A shell task failed with exit code 128: fatal: reference is not a tree: 2e38ee3e7a3e1dd01be31edb94368c2f4a840915

    Reviewed by MarvinNazari at 2015-11-07 14:16
  • 7. Upload Progress

    Sorry if i ask too many question here, wonder if there is any way we could get a progress when we upload a file like Alamofire upload method. Thanks 👍

    Reviewed by MarvinNazari at 2016-07-01 08:17
  • 8. Fix parameter type from [String:AnyObject] to [String:AnyObject?] for nullable value.

    Fixed parameter handling when its value is null.

    For example, there are some scenarios when some_api?query=&page=1 should be replaced as some_api?query&page=1 (no query=).

    Reviewed by inamiy at 2015-10-15 12:30
  • 9. [Q] `xcodebuild` with Swift Package Failed

    Hi

    Recently we are trying the use SwiftPackage of APIKit into our project. After importing, Xcode build successfully but the xcodebuild command part failed.

    By looking into the log, since APIKit have this code:

    #if SWIFT_PACKAGE
        class AbstractInputStream: InputStream {
            init() {
                super.init(data: Data())
            }
        }
    #endif
    

    and the overriding & super related code failed. It looks like it cannot be archive the correct super one.

    I would be appreciate if you could tell me how we could fix it.

    Best regards

    Here is the script of the failed log:

    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:170:22: property does not override any property from its superclass
    
            override var hasBytesAvailable: Bool {
                                          ^~~~~~~~~~~~~~
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:174:23: method does not override any method from its superclass
    
            override func read(_ buffer: UnsafeMutablePointer<UInt8>, maxLength: Int) -> Int {
            ~~~~~~~~     ^
    
    
    
    ❌  SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:127:37: use of undeclared type 'AbstractInputStream'
    
        internal class PartInputStream: AbstractInputStream {
            ~~~~~~~~      ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:259:22: property does not override any property from its superclass
    
            override var streamStatus: Stream.Status {
                                        ^~~~~~~~~~~~~~~~~~~
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:263:22: property does not override any property from its superclass
    
            override var hasBytesAvailable: Bool {
            ~~~~~~~~     ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:267:23: method does not override any method from its superclass
    
            override func open() {
            ~~~~~~~~     ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:271:23: method does not override any method from its superclass
    
            override func close() {
            ~~~~~~~~      ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:275:23: method does not override any method from its superclass
    
            override func read(_ buffer: UnsafeMutablePointer<UInt8>, maxLength: Int) -> Int {
            ~~~~~~~~      ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:315:22: property does not override any property from its superclass
    
            override var delegate: StreamDelegate? {
            ~~~~~~~~      ^
    
    
    
    ❌ SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:320:23: method does not override any method from its superclass
    
            override func schedule(in aRunLoop: RunLoop, forMode mode: RunLoop.Mode) {
            ~~~~~~~~     ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:324:23: method does not override any method from its superclass
    
            override func remove(from aRunLoop: RunLoop, forMode mode: RunLoop.Mode) {
            ~~~~~~~~      ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:215:42: use of undeclared type 'AbstractInputStream'
    
        internal class MultipartInputStream: AbstractInputStream {
            ~~~~~~~~      ^
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:51:33: cannot convert value of type 'MultipartFormDataBodyParameters.MultipartInputStream' to expected argument type 'InputStream'
    
                return .inputStream(inputStream)
                                             ^~~~~~~~~~~~~~~~~~~
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:54:48: cannot convert value of type 'MultipartFormDataBodyParameters.MultipartInputStream' to expected argument type 'InputStream'
    
                return .data(try Data(inputStream: inputStream))
                                    ^~~~~~~~~~~
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:154:13: 'super' members cannot be referenced in a root class
    
                super.init()
                                                   ^~~~~~~~~~~
    
    
    
    ❌  /SourcePackages/checkouts/APIKit/Sources/APIKit/BodyParameters/MultipartFormDataBodyParameters.swift:231:13: 'super' members cannot be referenced in a root class
    
                super.init()
    
    
    Reviewed by HevaWu at 2020-02-21 03:05
  • 10. Swift4.2 support

    Hi, there.

    RunLoopMode is remated to RunLoop.Mode at Swift4.2

    In MultipartFormDataBodyParameters.swift: I think these functions

        override func schedule(in aRunLoop: RunLoop, forMode mode: RunLoopMode) {
    
        }
    
        override func remove(from aRunLoop: RunLoop, forMode mode: RunLoopMode) {
    
        }
    

    should be

        #if swift(>=4.2)
        override func schedule(in aRunLoop: RunLoop, forMode mode: RunLoop.Mode) {
    
        }
    
        override func remove(from aRunLoop: RunLoop, forMode mode: RunLoop.Mode) {
    
        }
       #else 
        override func schedule(in aRunLoop: RunLoop, forMode mode: RunLoopMode) {
    
        }
    
        override func remove(from aRunLoop: RunLoop, forMode mode: RunLoopMode) {
    
        }
       #endif
    

    Thanks

    Reviewed by usk2000 at 2018-10-01 02:00
  • 11. Add defaultURLSession and its delegate instead of instancePair

    This resolves #13. This change makes implementation simpler and more obvious.

    • Add defaultURLSession that has a delegate with minimal implementation of NSURLSessionDataDelegate.
    • Remove instance pair, which is singleton instance pair of API and NSURLSession.

    Breaking changes:

    • API does not creates singleton instances for each API subclasses. If an API subclass needs a separated session, defaultURLSession should be overrided.
    • API.instance is no longer available.
    • API.session is no longer available.
    • Delegate methods implemented in API should be moved to custom delegate class, and its instance should be set to delegate of defaultURLSession.

    See the discussion on #13 for more details.

    Reviewed by ishkawa at 2015-03-16 11:53
  • 12. Merge branch 'develop/4.0' into 'master'

    As mentioned here, some very useful changes were lost from develop/4.0. This branch introduces these changes into master.

    They're breaking changes, so a major version update is required.

    We can alternatively branch off master into develop/6.0 and merge these changes there but I fear the same loss may happen again this way.

    Reviewed by davdroman at 2020-03-14 16:41
  • 13. Executing multipart request on same Part instance can cause waiting reading eternally

    This problem occurred on MultipartFormDataBodyParameters.Part(data: Data).

    The declaration code was like below:

    struct MyFormRequest: Request {
        let baseURL = URL(string: "https://example.com/")!
    
        let method: HTTPMethod = .get
    
        let path = "/"
    
        typealias Response = Void
    
        private let parts: [MultipartFormDataBodyParameters.Part]
    
        init(images: [UIImage]) throws {
            parts = try images.map {
                guard let data = $0.jpegData() else { throw AppError.invalidSession }
                return data
                }.enumerated().map { (i, data) in
                    MultipartFormDataBodyParameters.Part(
                        data: data,
                        name: "images[]",
                        mimeType: "image/jpeg",
                        fileName: "imagae\(i).jpg")
            }
        }
        var bodyParameters: BodyParameters? {
            return MultipartFormDataBodyParameters(
                parts: parts,
                boundary: "0123456789abcdef")
        }
    
        func response(from object: Any, urlResponse: HTTPURLResponse) throws {
        }
    }
    

    Then, call it like below:

    func execute(images: [UIImage]) {
       let request = MyFormRequest(images: images)
       Session.send(request: request) { result in
        if case .success = result { return }
        // retry the request. (with the same instance)
        Session.send(request: request) { result2 in
           // it will be never success or failure!!!
        }
       }
    }
    

    I noticed this problem occured because of the class MultipartFormDataBodyParameters reads InputStream without rewinding or renewing stream.

    # MultipartFormDataBodyParameters.swift:
    
                    case bodyRange:
                        if bodyPart.inputStream.streamStatus == .notOpen {
                            bodyPart.inputStream.open()
                        }
    
                        // this line never finished when calling `read` twice on same instance.
                        let readLength = bodyPart.inputStream.read(offsetBuffer, maxLength: availableLength) 
    
                        sentLength += readLength
                        totalSentLength += readLength
    

    So I resolved it by changing Request class like below finally.

    struct MyFormRequest: Request {
        let baseURL = URL(string: "https://example.com/")!
    
        let method: HTTPMethod = .get
    
        let path = "/"
    
        typealias Response = Void
    
        private let images: [Data] 
    
        init(images: [UIImage]) throws {
            images = try images.map {
                guard let data = $0.jpegData() else { throw AppError.invalidSession }
                return data
                }
            // Do not create Part instance while initialization.
        }
        var bodyParameters: BodyParameters? {
           // Recreate part instance for each request.
           let parts = images.enumerated().map { (i, data) in
                    MultipartFormDataBodyParameters.Part(
                        data: data,
                        name: "images[]",
                        mimeType: "image/jpeg",
                        fileName: "imagae\(i).jpg")
            }
            return MultipartFormDataBodyParameters(
                parts: parts,
                boundary: "0123456789abcdef")
        }
    
        func response(from object: Any, urlResponse: HTTPURLResponse) throws {
        }
    }
    

    To begin with, I think the request instance should not be reuse. But in our project, we have to reuse it. (We wrap APIKit with RxSwift. When the retryable error (network connection error or somethink) occurred, to retry the request, we have to re-subscribe the same stream(Observable) with same request instance)

    I hope this post can help someone.

    Environment: APIKit version 4.0.0, iOS 12.1.4, Xcode 10.2.1(10E1001)

    Reviewed by ecoopnet at 2019-06-17 06:55
  • 14. [question] Was develop/4.0 branch abandoned?

    I'm trying to update an app that depended on APIKit 4.0.0-beta.1 (swift 3) to current swift version. The app is using progressHandler. However, 4.0.0-beta.1 tag is on develop/4.0 branch. It seems that the branch is not merged into master and latest release tag 5.0.0.

    Reviewed by hiroshi at 2019-05-29 04:41
  • 15. How about using a private queue as default callback queue?

    Currently if user calls Session.send(request) without setting the callback queue, it will use main thread for processing the callback.

    But APIKit, in layered architecture designing, should be in the infrastructure layer or persistence layer; And on the other side, main queue in iOS, since it mainly handles UI process, is usually considered in the UI layer.

    Therefore, it's probably not a good idea to let APIKit know about the main queue. it's a UI world thing, which an infrastructure world thing probably should not know.

    And by using main queue, which may also be used by any object in the program, as the default queue, it's very easy to cause a deadlock if a user tries to use a semaphore to stop current thread until the response received.

    So my opinion is that in Session's initializer, maybe it's better to set the default value of callbackQueue to DispatchQueue.global() or DispatchQueue(label: someLabel)

    Thanks for considering this issue.

    Reviewed by el-hoshino at 2018-11-05 12:31
  • 16. 'NSInvalidArgumentException', reason: '*** -propertyForKey: only defined for abstract class

    Hi

    I found this exception in Crashlytics crash logs from my production app. I can reproduce and paste it bellow. But I cannot find how to fix it. So, I just report this.

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -propertyForKey: only defined for abstract class.  Define -[_TtCV6APIKit31MultipartFormDataBodyParameters20MultipartInputStream propertyForKey:]!'
    
    • Xcode 8.3.3
    • iOS 10.3 SDK
    • iPhone Plus (iOS 10.3 simulator)
    import UIKit
    import APIKit
    
    protocol HttpBinRequest: Request {
    
    }
    
    extension HttpBinRequest {
        var baseURL: URL {
            return URL(string: "https://httpbin.org")!
        }
    }
    
    struct PostRequest: HttpBinRequest {
        typealias Response = Any
    
        var method: HTTPMethod {
            return .post
        }
    
        var path: String {
            return "/post"
        }
    
        var bodyParameters: BodyParameters? {
            let p = try! MultipartFormDataBodyParameters.Part(
                value: "value",
                name: "name"
            )
    
            return MultipartFormDataBodyParameters(parts: [p], entityType: .inputStream)
        }
    
        func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
            return object
        }
    }
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let request = PostRequest()
    
            Session.send(request) { result in
                print(result)
                switch result {
                case .success(let response):
                    print(response)
                case .failure(let error):
                    print("error: \(error)")
                }
            }
        }
    }
    
    Reviewed by konomae at 2017-08-30 07:29
Related tags
DBNetworkStack is a network abstraction for fetching request and mapping them to model objects

DBNetworkStack Main Features ?? Typed network resources ?? Value oriented architecture ?? Exchangeable implementations ?? Extendable API ?? Composable

Jan 10, 2022
Dratini is a neat network abstraction layer.

Dratini Dratini is a neat network abstraction layer. If you are looking for a solution to make your network layer neat, Dratini is your choice. Dratin

Jan 29, 2022
Network abstraction layer written in Swift.
Network abstraction layer written in Swift.

Moya 14.0.0 A Chinese version of this document can be found here. You're a smart developer. You probably use Alamofire to abstract away access to URLS

Jun 23, 2022
Elegant network abstraction layer in Swift.
Elegant network abstraction layer in Swift.

Elegant network abstraction layer in Swift. 中文 Design Features Requirements Communication Installation Usage Base Usage - Target - Request - Download

Jan 29, 2022
Lightweight network abstraction layer, written on top of Alamofire
Lightweight network abstraction layer, written on top of Alamofire

TRON is a lightweight network abstraction layer, built on top of Alamofire. It can be used to dramatically simplify interacting with RESTful JSON web-

Jun 17, 2022
Network abstraction layer written in Swift.
Network abstraction layer written in Swift.

Moya 15.0.0 A Chinese version of this document can be found here. You're a smart developer. You probably use Alamofire to abstract away access to URLS

Jun 22, 2022
A type-safe, high-level networking solution for Swift apps
A type-safe, high-level networking solution for Swift apps

What Type-safe network calls made easy Netswift offers an easy way to perform network calls in a structured and type-safe way. Why Networking in Swift

Apr 27, 2022
Sherlock Holmes of the networking layer. :male_detective:
Sherlock Holmes of the networking layer. :male_detective:

ResponseDetective is a non-intrusive framework for intercepting any outgoing requests and incoming responses between your app and your server for debu

Jun 14, 2022
Advanced Networking Layer Using Alamofire with Unit Testing

Advanced Networking Layer Using Alamofire with Unit Testing

May 23, 2022
An elegant yet powerful iOS networking layer inspired by ActiveRecord.
An elegant yet powerful iOS networking layer inspired by ActiveRecord.

Written in Swift 5 AlamoRecord is a powerful yet simple framework that eliminates the often complex networking layer that exists between your networki

Aug 7, 2020
GXBaseAPI - GARPIX Networking Layer

GXBaseAPI GARPIX Networking Layer URLSession + Combine + Codable + Generics Все

Jan 21, 2022
A barebones Swift HTTP client with automatic JSON response parsing.

HTTP Client A barebones Swift HTTP client with automatic JSON response parsing. Installation Add HTTP Client as a dependency through Xcode or directly

Feb 3, 2022
An Alamofire extension which converts JSON response data into swift objects using EVReflection

AlamofireJsonToObjects ?? This is now a subspec of EVReflection and the code is maintained there. ?? You can install it as a subspec like this: use_fr

Jun 3, 2021
Elegant API Abstraction for Swift

Endpoint (Deprecated) ⚠️ This project has been deprecated. Consider using Moya and MoyaSugar instead. ?? Elegant API Abstraction for Swift. At a Glanc

Mar 29, 2019
APIProvider - API Provider Package for easier API management inspired by abstraction

APIProvider Using APIProvider you can easily communicate with all API endpoints

Apr 21, 2022
Swift-multipart-formdata - MultipartFormData: Build multipart/form-data type-safe in Swift

MultipartFormData Build multipart/form-data type-safe in Swift. A result builder

Jun 9, 2022
This package is meant to make http request of an easy way inspiren in the architecture of Moya package

NetworkAgent This package is meant to make http request of an easy way inspiren in the architecture of Moya package. This package is 100% free of depe

Jun 5, 2022
Minimalistic Swift HTTP request agent for iOS and OS X

Agent Table of Contents Introduction Usage HTTP Verbs Overloading Method Chaining Response Closure Verbs Methods NSMutableURLRequest Contributing Lice

Apr 14, 2022
Automatically sets the network activity indicator for any performed request.

BigBrother BIG BROTHER IS WATCHING YOU. BigBrother is a Swift library made for iOS that automatically watches for any performed request and sets the n

Apr 23, 2022