9 years ago

Paginated API requests using Functional Reactive in Swift

I’ve been playing the days with Reactive Cocoa. I fell in love with that programming paradigm. I had heard about it before but hadn’t stopped to play a little bit with it. Although it might be scary at first, and most of the concepts are difficult to understand when you first take a look at them. The more you get familiarized with it the more you think in term of streams.

In order to practice a little bit with reactive programming I implemented an API client offering a public reactive interface. That client has methods that instead of using blocks to notify the completion of the API request, return a signal which is executed when someone subscribes to that signal. That API client pointed to an API that offered paginated responses, i.e. having execute different requests to get all the resources if the results number is higher than the page limit.

Taking advantange of the reactive approach of the client I implemented that paginated method and made it resusable for any client independent from the http framework you are using. Let’s see how I did it:

typealias PaginatedRequest = (page: Int, pageLimit: Int) -> RACSignal

internal func rac_paginatedSignal(initialPage: Int, pageLimit: Int, requestSignal: PaginatedRequest) -> RACSignal {
    var currentPage = initialPage
    let nextSignal = { () -> RACSignal in
        let signal = requestSignal(page: currentPage, pageLimit: pageLimit)
        currentPage = currentPage + 1
        return signal
    }
    var subscribeNext: ((RACSubscriber!) -> Void)?
    subscribeNext = { (s: RACSubscriber!) -> Void in
        nextSignal().subscribeNext({ (response) -> Void in
            if let items = response as? [AnyObject] {
                for item in items {
                   s.sendNext(item)
                }
                if items.count == pageLimit {
                    subscribeNext!(s)
                }
            }
            else {
                s.sendError(NSError(domain: "invalid.response", code: -1, userInfo: nil))
            }
        }, error: { (error) -> Void in
            s.sendError(error)
        }, completed: { () -> Void in
            s.sendCompleted()
        })
    }
    return RACSignal.createSignal({ (subscriber) -> RACDisposable! in
        subscribeNext!(subscriber)
        return nil
    })
}

Breaking down

typealias PaginatedRequest = (page: Int, pageLimit: Int) -> RACSignal
var currentPage = initialPage
let nextSignal = { () -> RACSignal in
    let signal = requestSignal(page: currentPage, pageLimit: pageLimit)
    currentPage = currentPage + 1
    return signal
}
var subscribeNext: ((RACSubscriber!) -> Void)?
subscribeNext = { (s: RACSubscriber!) -> Void in
    nextSignal().subscribeNext({ (response) -> Void in
        if let items = response as? [AnyObject] {
            for item in items {
               s.sendNext(item)
            }
            if items.count == pageLimit {
                subscribeNext!(s)
            }
        }
        else {
            s.sendError(NSError(domain: "invalid.response", code: -1, userInfo: nil))
        }
    }, error: { (error) -> Void in
        s.sendError(error)
    }, completed: { () -> Void in
        s.sendCompleted()
    })
}
RACSignal.createSignal({ (subscriber) -> RACDisposable! in
  subscribeNext!(subscriber)
  return nil
})

Important notes

ReactiveCocoa is very useful when you’re dealing with asynchronnous events because you can manipulate and combine them easily as you’re receiving them. In that case we have different streams that we combine in a single stream we’re we’re receiving the collection items.

If you want to use Reactive programming in your projects and don’t know how, or you wanna talk about anything related with that, drop me a line, pedropb@hey.com

About Pedro Piñera

I created XcodeProj and Tuist, and co-founded Tuist Cloud. My work is trusted by companies like Adidas, American Express, and Etsy. I enjoy building delightful tools for developers and open-source communities.