Search code examples
swiftalamofire

Proper model for multiple Alamofire requests for multiple websites


I am using Alamofire to scrape web pages for some data, let’s say News. News is a generic object with something like title, content, picture, date, author etc. However for each web site, I use different method. For some I use json for others I use hpple to extract the data. How can I create a some kind of service for each website. Should I create different Services for each web site or is there a better way to use some kind of generic function templates for each web site. Like

Login()
Fetch()
Populate()
return News(…..)

Then after I create the news and populate the tableview, how can I refresh the News object? Since News is generic, it can’t know who created it with which method.


Solution

  • There are many ways to design this type of abstraction. I tend to lean towards simplicity as much as possible in my architectural designs if possible. A great pattern here is to use a Service object with class methods to handle calling your different services, parsing the result and calling a success or failure closure.

    You can also use a completion handler that doesn't split the success and failure into two things, but then you need to handle the failure or success in your caller objects which I don't really like. Here's an example of the Service design in action.

    FirstNewsService

    import Alamofire
    
    struct News {
        let title: String
        let content: String
        let date: NSDate
        let author: String
    }
    
    class FirstNewsService {
    
        typealias NewsSuccessHandler = ([News]) -> Void
        typealias NewsFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void
    
        // MARK: - Fetching News Methods
    
        class func getNews(#success: NewsSuccessHandler, failure: NewsFailureHandler) {
            login(
                success: { apiKey in
                    FirstNewsService.fetch(
                        apiKey: apiKey,
                        success: { news in
                            success(news)
                        },
                        failure: { response, json, error in
                            failure(response, json, error)
                        }
                    )
                },
                failure: { response, json, error in
                    failure(response, json, error)
                }
            )
        }
    
        // MARK: - Private - Helper Methods
    
        private class func login(#success: (String) -> Void, failure: (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
            let request = Alamofire.request(.GET, "login/url")
            request.responseJSON { _, response, json, error in
                if let error = error {
                    failure(response, json, error)
                } else {
                    // NOTE: You'll need to parse here...I would suggest using SwiftyJSON
                    let apiKey = "12345678"
                    success(apiKey)
                }
            }
        }
    
        private class func fetch(
            #apiKey: String,
            success: ([News]) -> Void,
            failure: (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void)
        {
            let request = Alamofire.request(.GET, "fetch/url")
            request.responseJSON { _, _, json, error in
                if let error = error {
                    failure(response, json, error)
                } else {
                    // NOTE: You'll need to parse here...I would suggest using SwiftyJSON
                    let news = [News]()
                    success(news)
                }
            }
        }
    }
    

    Inside a View Controller

    override func viewDidLoad() {
        super.viewDidLoad()
    
        FirstNewsService.getNews(
            success: { news in
                // Do something awesome with that news
                self.tableView.reloadData()
            },
            failure: { response, json, error in
                // Be flexible here...do you want to retry, pull to refresh, does it matter what the response status code was?
                println("Response: \(response)")
                println("Error: \(error)")
            }
        )
    }
    

    Feel free to mod the design however you like to tailor it to your use cases. None of this pattern is set in stone. It just gives you a common way to construct different services. @mattt also has some really cool patterns (Router and CRUD) in the Alamofire README which I would highly recommend reading through. They are definitely more complicated though and still require a Service type of object to maximize code reuse.

    Hopefully that helps shed some light.