Reputation: 62
The question is how can I make this code reusable especially the error checking in the network method and the condition in the completionhandler, so I don't have duplicate code?
I created a method which makes a network request with URLSession and calls a completion handler with the statuscode as argument. In the completion handling, I created a condition which shows an error message or perfom a segue based on the statuscode. All of this code works but I want to make it reusable so I don't have duplicate code.
Networking method:
func saveMessage(data: String, day: String, completion: @escaping (Int)->()) {
let url = URL(string: "\(Constants.baseURL)/daily_mindset/today_message")
guard let requestUrl = url else { fatalError() }
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
// Set HTTP Request Header
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonData = encodeJSON(with: data, day: day)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
completion(700)
return
}
guard let response = response as? HTTPURLResponse else {
completion(701)
return
}
guard (200...299).contains(response.statusCode) else {
completion(response.statusCode)
return
}
guard let mime = response.mimeType, mime == "application/json" else {
completion(702)
return
}
guard let data = data else {
completion(703)
return
}
do {
let todoItemModel = try JSONDecoder().decode(MessageData.self, from: data)
Constants.currentMindsetId = todoItemModel._id!
print("Response data:\n \(todoItemModel)")
} catch let jsonErr{
print(jsonErr)
}
completion(response.statusCode)
}
task.resume()
}
Calling networking method with completionhandler:
messageManager.saveMessage(data: textView.text, day: day, completion: {(statusCode: Int) -> Void in
if (200...299).contains(statusCode) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ToDailyMindsetScreen", sender: sender)
}
} else if (400...499).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Please make sure you filled in the all the required fields."
}
} else if (500...599).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Sorry, couldn't reach our server."
}
} else if (700...).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Sorry, something went wrong. Try again later."
}
}
})
Code in the networking method I want to reuse:
if error != nil {
completion(700)
return
}
guard let response = response as? HTTPURLResponse else {
completion(701)
return
}
guard (200...299).contains(response.statusCode) else {
completion(response.statusCode)
return
}
guard let mime = response.mimeType, mime == "application/json" else {
completion(702)
return
}
guard let data = data else {
completion(703)
return
}
Code in the completionhandler I want to reuse:
if (200...299).contains(statusCode) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ToDailyMindsetScreen", sender: sender)
}
} else if (400...499).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Please make sure you filled in the all the required fields."
}
} else if (500...599).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Sorry, couldn't reach our server."
}
} else if (700...).contains(statusCode) {
DispatchQueue.main.async {
self.errorLabel.text = "Sorry, something went wrong. Try again later."
}
}
Upvotes: 1
Views: 2060
Reputation: 483
If the error messages are ViewController specific you can start with creating a function that returns the message based on the status code like this:
private func getErrorMessageFor(statusCode: Int) -> String? {
if (200...299).contains(statusCode) {
//If no error message is returned assume that the request was a success
return nil
} else if (400...499).contains(statusCode) {
return "Please make sure you filled in the all the required fields."
} else if (500...599).contains(statusCode) {
return "Sorry, couldn't reach our server."
} else if (700...).contains(statusCode) {
return "Sorry, something went wrong. Try again later."
} else {
return "Message for other errors?"
}
}
You can always move this code to a ViewController subclass to provide more generic error messages and override it later to provide more detailed errors for a specific View Controller.
class BaseViewController: UIViewController {
func getErrorMessageFor(statusCode: Int) -> String? {
//base implementation here
}
}
class OtherViewController: BaseViewController {
override func getErrorMessageFor(statusCode: Int) -> String? {
//create a new error message only for statusCode 404
if statusCode == 404 {
return "The requested resource was not found on the server. Please contact the support team"
} else {
return super.getErrorMessageFor(statusCode: statusCode)
}
}
}
Keep in mind that as your app grows you might want to create an APIClient that would handle networking and error handling for you. Take a look at https://bustoutsolutions.github.io/siesta/, it is very user friendly
Upvotes: 1