I am using Moya to make network calls in Swift.
In my ViewController
I am calling the WebService and creating a model from response.
import UIKit
import Moya
class LoginViewController: UIViewController {
let provider = MoyaProvider<Api>()
override func viewDidLoad() {
super.viewDidLoad()
generateAccessToken()
}
private func generateAccessToken() {
provider.request(.generateAccessToken(
mobile: phoneTextField.text!,
countryCode: "91",
name: "Saurabh",
otp: "9612")
) { result in
switch result {
case .success(let response):
let data = response.data
let user = try? JSONDecoder().decode(User.self, from: data)
if let authToken = user?.authToken {
AuthManager.setToken(token: authToken)
}
case .failure(let error):
print(error.response?.statusCode as Any)
}
}
}
I want to move the WebService call to other file, say NetworkManager.swift
import Foundation
import Moya
struct NetworkManager {
public static let shared = NetworkManager()
private let provider = MoyaProvider<Api>()
func generateAccessToken(with mobile: String, countryCode: String, name: String, otp: String) {
// Get result and return to calling class
}
}
But it is strongly recommended not to use Singleton Class.
How should I call my WebService and return the response to the LoginViewController
?
3 Answers 3
I would recommend looking into MVVM architecture. You can see a simple example in this repo.
One problem with it is that its author declares and assigned view models directly in a ViewController
which can be a bit problematic when writing tests. A better approach would be to inject view model so it can be easily replaced with mock. To improve that approach you can take a look at coordinators patterns, a bit more advanced example can be found here
In most cases the WebService
should go in the ViewModel
in the MVVM pattern. Services can be injected to ViewModels
, which are responsible for managing data provided by these services. Then when they perform all logical transformations (filtering, ordering, multiplying etc.) they provide this information to a view (ViewController
in our case) which just consumes it. It allows us to easily write mocks and tests. There are of course more approaches like state machine, but ViewModel
as content provider and meeting point for services is most common and most universal one in my opinion.
-
\$\begingroup\$ Thanks for this. I have reading about MVVM. Just a quick clarification: should the WebService go in ViewModel in MVVM pattern? \$\endgroup\$saurabh– saurabh2020年04月12日 17:06:03 +00:00Commented Apr 12, 2020 at 17:06
-
\$\begingroup\$ In most cases - yes. Services can be injected to ViewModels, which are responsible for managing data provided by these services. Then when they perform all logical transformations (filtering, ordering, multiplying etc.) they provide this information to a view (ViewController in our case) which just consumes it. It allows us to easily write mocks and tests. There are of course more approaches like state machine, but ViewModel as content provider and meeting point for services is most common and most universal one in my opinion. \$\endgroup\$Kamajabu– Kamajabu2020年04月12日 17:10:34 +00:00Commented Apr 12, 2020 at 17:10
I think what you are missing is dependency injection and dependency inversion.
You should have something like
let keychainService = KeychainService()
let apiClient = ApiClient(keychainService: keychainService)
let loginService = LoginService(apiClient: apiClient, keychainService: keychainService)
let loginViewController = LoginViewController(loginService: loginService)
everything about headers and http goes to ApiClient, as well as adding the token to some requests, and retries. everything about requesting a new user goes to loginService. for reading and saving tokens and perhaps current user info, you can use KeychainService.
Note that you have to use protocols, so, the view controller relies on an abstraction, not a concrete implementation.
To manage this, you can use factory methods (a method that returns the view controller properly initialised) or frameworks like winject or DIP.
BTW, when using dependency inversion and injection, it is super easy to apply unit testing. You can very easily to create a mock of the login service and test that your view controller works fine.
Could you make the following changes:
1) Create a file named LoginViewModel.swift
func getAccessToken(with mobile: String, code: String, otp: String, name: String, completion: @escaping(Bool, String)->Void) {
networkManager.getAccessTokenFrom(mobile: mobile, code: String, otp: code, name: name) { (isSuccess, error) in
if isSuccess {
// Do neccessary logic
// Set access token logic here
completion(true, "")
}
else {
completion(false, error.description)
}
}
}
2) Create a similar function in NetworkManager class to interact with Moya framework
let provider = MoyaProvider<Api>()
func getAccessTokenFrom(mobile: String, code: String, otp: String, name: String, completion: @escaping(error, String)->Void) {
provider.request(.generateAccessToken(
mobile: mobile,
countryCode: code,
name: name,
otp: otp)
) { result in
switch result {
case .success(let response):
let data = response.data
let user = try? JSONDecoder().decode(User.self, from: data)
if let authToken = user?.authToken {
completion(true, authToken)
}
case .failure(let error):
completion(false, error.description)
}
}}
}
- Inside LoginViewController.swift:
let loginViewModel = LoginViewModel()
func viewDidload() {
// start activity loader
loginViewModel.getAccessToken(with mobile: mobileTField.text, code: codeTField.text, otp: otpTField.text, name: nameTField.text) { (isSuccess, error) in
if isSuccess {
// Hide activity loader
}
else {
// Show alert based on the "error" variable value
}
}
}
-
2\$\begingroup\$ Welcome to code review. Good answers are observations about the code, and not necessarily alternate implementations. It would improve your answer, if you explained why each of these modifications are necessary. A good answer MUST include at least one observation about the code, and this answer doesn't contain any observations about the code. \$\endgroup\$2020年04月19日 16:22:22 +00:00Commented Apr 19, 2020 at 16:22
Explore related questions
See similar questions with these tags.