- Swift 4
- iOS 10.3 and above
The demo project shows how to implement a few different ways of dealing with web calls using this library. The custom request manager is only needed if you intend to be able to easily switch between different data sources.
SimpleWebRequests is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod'SimpleWebRequests'Add all of the files in the 'Sources' directory to your project and you'll be ready to go.
This library allows you to have your web layer up and running quickly, but also allows for a more complex setup involving multiple data sources (release, development, local, mock), but more on that later.
To get started all you need to do is add a subclass of 'DataResource' for each of your web requests to your project. I recommend adding a file called '{ProjectName}DataResources.swift and putting your resources in this file as shown below:
Note: The header, query and resource parameters are usually not static and will be set using 'setHeaders:', 'setBody:' and 'setQuery:' at the time a resource is instatiated and used.
// {ProjectName}ApiResources.swift
structGetArticlesResource:DataResource{typealiasModel=[Article]varurlSessionConfiguration:URLSessionConfiguration=URLSessionConfiguration.default
varhttpMethod:HttpMethod=.get
varheaders:[String:String]?varbaseUrl:String="https://www.test.com"varmethodPath:String?="api/test"varquery:String?varbody:Data?}After setting up your resources you are ready to make some web calls as shown below:
letresource=GetArticlesResource()letsessionTask=NetworkDataRequest(resource: resource).load{(response)inswitch response {
case let.success(model):
// Do something with the model coming back
case let.error(error):
// Handle general error
caselet.httpError(statusCode):
// Handle HTTP error
}}If your request requires a body set the body of the resource directly in the class definition if they are non-changing or as shown below:
//let resource = AddArticlesResource()
letbody=Article(title:"Test 1", description:"Testing article description.")
resource.setBody(body:JSONCoder.encode(object: body))
//let sessionTask = NetworkDataRequest(resource: resource).load { (response) in
//...If your request requires a query set the query of the resource directly in the class definition if they are non-changing or as shown below:
//let resource = AddArticlesResource()
resource.setQuery(query:"?something=something")
//let sessionTask = NetworkDataRequest(resource: resource).load { (response) in
//...If your request requires headers set the headers of the resource directly in the class definition if they are non-changing or as shown below:
//let resource = AddArticlesResource()
resource.setHeaders(headers:["SomeHeaderKey":"SomeHeaderValue"])
//let sessionTask = NetworkDataRequest(resource: resource).load { (response) in
//...The advanced setup lets you automatically switch between different data sources through a common manager class. For this setup you will need to inherit from the following structs/protocols. Again I'm recommending to prefix your classes with your project name for claity.
protocol ApiInformation
protocol DataRequestManager
class MockDataRequestManager (optional)
class LocalDataRequestManager (optional)
Let's start with the ApiInformation. This class will let you set your desired data source that's used at runtime. In the below example I'm switching between data sources depending on if my app is running in debug or in release as set in my apps build configuration.
// {ProjectName}ApiInformation.swift
struct{ProjectName}ApiInformation: ApiInformation {#if DEBUGvardataSource:DataSource=DataSource(type:.development, baseUrl:"http://development.something.com")#elsevardataSource:DataSource=DataSource(type:.release, baseUrl:"https://release.something.com")#endif}The next part to be setup is the data request manager that handles the data source switching as shown below:
Note: The online data request manager will be set to a new instance of NetworkDataRequestManager unless you desire to to make your own class inheriting from it to make desired changes to the class.
// {ProjectName}DataRequestManager.swift
publicstruct{ProjectName}DataRequestManager: DataRequestManager {
public typealias Resource = DataResource
public varapiInformation:ApiInformation={ProjectName}ApiInformation()
public varonlineDataRequestManager:NetworkDataRequestManager=NetworkDataRequestManager()
public varlocalDataRequestManager:LocalDataRequestManager?={ProjectName}LocalDataRequestManager()
public varmockDataRequestManager:MockDataRequestManager?={ProjectName}MockDataRequestManager()
public static varshared:{ProjectName}DataRequestManager={ProjectName}DataRequestManager()}The mock as well as the local request manager are setup very similarly and really depend on your implementation and how you want to handle these data requests as this library only provides a shell for these parts so that the data request manager can switch based on data sources. Below is an example of what each may look like:
Mock Data Request Manager
Note: In the switch statement I'm showing how each resource case can be handled based on its type. Make sure to generate and return your appropriate mock data for each case statement.
// {ProjectName}MockDataRequest.swift
publicclass{ProjectName}MockDataRequestManager: MockDataRequestManager {
// All your custom variables to allow mocking your server
vararticles:[Article]=[Article(title:"Mock 1", description:"Mocking an article."),Article(title:"Mock 2", description:"Mocking an article again.")]
public overridefunc loadRequest<Resource:DataResource>(with resource:Resource, completion:@escaping(DataResponse<Resource.Model>)->Void)->URLSessionTask{returnhandleRequest(with: resource, completion: completion)}}extension{ProjectName}MockDataRequestManager {
// MARK: - Handle Request
func handleRequest<Resource:DataResource>(with resource:Resource, completion:@escaping(DataResponse<Resource.Model>)->Void)->URLSessionTask{switch resource.self {case is GetArticlesResource:getArticles(completion: completion as!(DataResponse<GetArticlesResource.Model>)->Void)default:returnURLSessionTask()}returnURLSessionTask()}}extension{ProjectName}MockDataRequestManager {
// MARK: - Articles (GET)
func getArticles(completion:@escaping(DataResponse<GetArticlesResource.Model>)->Void){letresponse:DataResponse=.success(model: articles)completion(response)}}Local Data Request Manager
Note: In the switch statement I'm showing how each resource case can be handled based on its type. Make sure to return your appropriate data from a local database for example for each case statement.
// {ProjectName}LocalDataRequest.swift
publicclass{ProjectName}LocalDataRequestManager: LocalDataRequestManager {
public override func loadRequest<Resource:DataResource>(with resource: Resource, completion:@escaping(DataResponse<Resource.Model>)-> Void)-> URLSessionTask {
// Get offline data
returnhandleRequest(with: resource, completion: completion)}}extension{ProjectName}LocalDataRequestManager {
// MARK: - Handle Request
func handleRequest<Resource:DataResource>(with resource:Resource, completion:@escaping(DataResponse<Resource.Model>)->Void)->URLSessionTask{switch resource.self {case is GetArticlesResource:leterror=NSError(domain:"Local Data Fetch Error", code:1, userInfo:nil)letresponse:DataResponse= DataResponse<Resource.Model>.error(error: error)completion(response)default:returnURLSessionTask()}returnURLSessionTask()}}This is all of the setup needed for the advanced setup. Below is an example on how you would handle a call using the advanced setup.
letresource=GetArticlesResource()letsessionTask={ProjectName}DataRequestManager.shared.loadRequest(with: resource){(response)inswitch response {
case let.success(model):
// Do something with the model coming back
case let.error(error):
// Handle general error
caselet.httpError(statusCode):
// Handle HTTP error
}}There is one more thing to consider to help with clean code when calling one of these requests. Consider extending your data request manager and add functions that represent what each of your requests does and that handles the setup of your resource including the setting of your headers, body, and query. And example of your this would look like including how to call it is shown below:
*Note: I have extended my resource class to generate my query parameter based on certain parameters.
// {ProjectName}DataRequestManager.swift
extension{ProjectName}DataRequestManager {func getArticles(completion:@escaping(DataResponse<[Article]>)->Void)->URLSessionTask{letresource=GetArticlesResource()
resource.setQuery(query: resource.articlesQuery())return{ProjectName}DataRequestManager.shared.loadRequest(with: resource, completion: completion)}
// Example including setting a body
func addArticle(with body:Article, completion:@escaping(DataResponse<Article>)->Void)->URLSessionTask{letresource=AddArticlesResource()
resource.setBody(body:JSONCoder.encode(object: body))return{ProjectName}DataRequestManager.shared.loadRequest(with: resource, completion: completion)}}
// {ProjectName}DataResources.swift
extensionGetArticlesResource{func articlesQuery(with id:Int)->String{return"?id=\(id)"}}
// SomeFile.swift
letsessionTask={ProjectName}DataRequestManager.shared.getArticles{(response)inswitch response {
case let.success(model):
// Do something with the model coming back
case let.error(error):
// Handle general error
caselet.httpError(statusCode):
// Handle HTTP error
}}Niklas Fahl (fahlout) - LinkedIn
Inspired by: http://mrgott.com/swift-programing/33-rest-api-in-swift-4-using-urlsession-and-jsondecode
SimpleWebRequests is available under the MIT license. See the LICENSE file for more info.