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.
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.