SkillAgentSearch skills...

FranticApparatus

Promises framework for Swift 5

Install / Use

/learn @jkolb/FranticApparatus

README

FranticApparatus

Carthage compatible

A thread safe, type safe, and memory safe Promises/A+ implementation for Swift 5

Here are some examples pulled directly from the included Demo code.

Building a promise that returns the result of a network request:

import Foundation
import FranticApparatus

public struct NetworkResult {
    public let response: URLResponse
    public let data: Data
}

public enum NetworkError : Error {
    case unexpectedData(Data)
    case unexpectedResponse(URLResponse)
    case unexpectedStatusCode(Int)
    case unexpectedContentType(String)
}

public protocol NetworkLayer : class {
    func requestData(_ request: URLRequest) -> Promise<NetworkResult>
}

public final class SimpleURLSessionNetworkLayer : NetworkLayer {
    private let session: URLSession

    public init() {
        let sessionConfiguration = URLSessionConfiguration.default
        self.session = URLSession(configuration: sessionConfiguration)
    }

    deinit {
        session.invalidateAndCancel()
    }

    public func requestData(_ request: URLRequest) -> Promise<NetworkResult> {
        return Promise<NetworkResult> { (fulfill, reject) in
            session.dataTask(with: request, completionHandler: { (data, response, error) in
                if let error = error {
                    reject(error)
                }
                else if let data = data, let response = response {
                    fulfill(NetworkResult(response: response, data: data))
                }
                else {
                    fatalError("Unexpected")
                }
            }).resume()
        }
    }
}

Chaining off of a network request promise to display thumbnails in a collection view:

func loadImage(at indexPath: IndexPath) {
    let model = models[indexPath.item]

    ApplicationNetworkActvityIndicator.shared.show()
    promises[indexPath] = fetchImage(url: model.url).then(on: DispatchQueue.main, { (image) in
        self.images[indexPath] = image
        self.showImage(at: indexPath)
    }).catch(on: DispatchQueue.main, { (error) in
        self.errors[indexPath] = error
        self.showError(at: indexPath)
    }).finally(on: DispatchQueue.main, {
        ApplicationNetworkActvityIndicator.shared.hide()
        self.promises[indexPath] = nil
    })
}

func fetchImage(url: URL) -> Promise<UIImage> {
    return networkLayer.requestData(URLRequest(url: url)).then(on: processQueue, map: { (result) in
        guard let httpResponse = result.response as? HTTPURLResponse else {
            throw NetworkError.unexpectedResponse(result.response)
        }

        guard Set<Int>([200]).contains(httpResponse.statusCode) else {
            throw NetworkError.unexpectedStatusCode(httpResponse.statusCode)
        }

        let contentType = httpResponse.mimeType ?? ""

        guard Set<String>(["image/jpeg", "image/png"]).contains(contentType) else {
            throw NetworkError.unexpectedContentType(contentType)
        }

        guard let image = UIImage(data: result.data) else {
            throw NetworkError.unexpectedData(result.data)
        }

        return image
    })
}

func showImage(at indexPath: IndexPath) {
    if let cell = collectionView.cellForItem(at: indexPath) as? ImageCell {
        cell.hideActivity()
        cell.image = images[indexPath]
    }
}

func showError(at indexPath: IndexPath) {
    if let cell = collectionView.cellForItem(at: indexPath) as? ImageCell {
        cell.hideActivity()

        if let error = errors[indexPath] {
            cell.error = messageFor(error: error)
        }
        else {
            cell.error = nil
        }
    }
}

Loading JSON, parsing it, and then waiting for all thumbnails to load before displaying them:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if thumbnailsPromise == nil {
        ApplicationNetworkActvityIndicator.shared.show()
        thumbnailsPromise = fetchThumbnails().then(on: DispatchQueue.main, { (thumbnails) in
            self.thumbnails = thumbnails
            self.collectionView.reloadData()
        }).catch(on: DispatchQueue.main, { (error) in
            NSLog("\(error)")
        }).finally(on: DispatchQueue.main, {
            ApplicationNetworkActvityIndicator.shared.hide()
            self.thumbnailsPromise = nil
        })
    }
}

func fetchThumbnails() -> Promise<[UIImage]> {
    return networkLayer.requestData(URLRequest(url: URL(string: "https://reddit.com/.json")!)).then(on: processQueue, promise: { (result) in
        guard let httpResponse = result.response as? HTTPURLResponse else {
            throw NetworkError.unexpectedResponse(result.response)
        }

        guard Set<Int>([200]).contains(httpResponse.statusCode) else {
            throw NetworkError.unexpectedStatusCode(httpResponse.statusCode)
        }

        let contentType = httpResponse.mimeType ?? ""

        guard Set<String>(["application/json"]).contains(contentType) else {
            throw NetworkError.unexpectedContentType(contentType)
        }

        let object = try JSONSerialization.jsonObject(with: result.data, options: [])

        guard let dictionary = object as? NSDictionary else {
            throw NetworkError.unexpectedData(result.data)
        }

        let thumbnailURLs = try self.thumbnailsFromJSON(object: dictionary)
        let thumbnailPromises = thumbnailURLs.map({ self.fetchImage(url: $0) })
        
        return all(context: self.processQueue, promises: thumbnailPromises)
    })
}

func thumbnailsFromJSON(object: NSDictionary) throws -> [URL] {
    guard let data = object["data"] as? NSDictionary else { throw JSONError.unexpectedJSON }
    guard let children = data["children"] as? NSArray else { throw JSONError.unexpectedJSON }
    var thumbnailURLs = [URL]()
    thumbnailURLs.reserveCapacity(children.count)

    for child in children {
        if let childObject = child as? NSDictionary {
            if let childData = childObject["data"] as? NSDictionary {
                if let thumbnail = childData["thumbnail"] as? NSString {
                    if thumbnail.hasPrefix("http") {
                        if let thumbnailURL = URL(string: thumbnail as String) {
                            thumbnailURLs.append(thumbnailURL)
                        }
                    }
                }
            }
        }
    }

    return thumbnailURLs
}

Contact

Justin Kolb
@nabobnick

License

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

Related Skills

View on GitHub
GitHub Stars25
CategoryDevelopment
Updated5y ago
Forks0

Languages

Swift

Security Score

80/100

Audited on Nov 17, 2020

No findings