MVVM-C Architecture
3 min readSep 24, 2020
It enables to write modular, reactive programming, clean, maintainable, and testable code.
Understanding MVVM
MVVM -C is composed of four main components:
Model:
- The Model represents the data and business logic of the application.
- It includes data structures, databases, network calls, and other components responsible for data management.
- The Model is independent of the user interface and the ViewModel.
View:
- This is the user interface layer where the user interacts with the app.
- It includes UI components such as buttons, labels, text fields, and views.
- The View is responsible for displaying data and capturing user input.
ViewModel:
- The ViewModel acts as an intermediary between the View and the Model.
- It holds the presentation logic and business logic related to the View.
- The ViewModel prepares and provides data to the View for display.
- It also handles user input and communicates with the Model for data manipulation.
- ViewModels can be reused across multiple Views since they are independent of UI components.
App Coordinator:
Manages the overall flow of the application.
Below you can find the Big Scale Blueprint and Small Scale Blueprint of MVVM Architecture.
Example with Big Scale
Pros:
- Mvvm makes the view controller simpler by moving a lot of business logic out of it.
- The view model better expresses the business logic for the view.
- View model is much easier to test then a view controller.
Con’s:
- For Beginners, MVVM will be hard to implement.
- Apps with simple UI, MVVM can be overkill.
- For larger apps, data binding will complex, So debugging will be hard
Create API manager
enum ErrorType: Error {
case decodeFailed
}
enum HTTPSMethod: String {
case GET = "GET"
case POST = "POST"
}
protocol USERRequest {
associatedtype Response: Decodable
var url: URL {get}
var httpsMethod: HTTPSMethod { get set }
}
protocol APIManagerProtocol {
func getData<T: USERRequest>(request: T, completion:@escaping(Result<T.Response, ErrorType>)->Void)
}
class APIManager: APIManagerProtocol {
private let https: HTTPSClientProtocol?
private let decodeData: DecodeDataProtocol?
init(https: HTTPSClientProtocol?, decodeData: DecodeDataProtocol?) {
self.https = https
self.decodeData = decodeData
}
func getData<T: USERRequest>(request: T, completion:@escaping(Result<T.Response, ErrorType>)->Void) {
https?.getDataFromServer(url: request.url, methodType: request.httpsMethod, completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let data):
self.decodeData?.decode(data: data, completionHandler: { (decodeData: Result<T.Response, ErrorType>) in
switch decodeData {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(.decodeFailed))
}
})
case .failure(let error):
completion(.failure(error))
}
})
}
}
protocol HTTPSClientProtocol {
func getDataFromServer(url: URL, methodType: HTTPSMethod, completion:(Result<Data, ErrorType>)->Void)
}
class HTTPSClient: HTTPSClientProtocol {
func getDataFromServer(url: URL, methodType: HTTPSMethod, completion:(Result<Data, ErrorType>)->Void) {
// Mock API Call
let mockData = """
{ "message": "Hello, World!" }
""".data(using: .utf8)
if let data = mockData {
completion(.success(data))
} else {
completion(.failure(.decodeFailed))
}
}
}
protocol DecodeDataProtocol {
func decode<T: Decodable>(data: Data, completionHandler: @escaping (Result<T, ErrorType>)->Void)
}
class DecodeData: DecodeDataProtocol {
func decode<T: Decodable>(data: Data, completionHandler:@escaping (Result<T, ErrorType>)->Void) {
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
completionHandler(.success(decodedData))
} catch {
}
}
}
struct SampleResponse: Decodable {
let message: String
}
struct SampleRequest: USERRequest {
typealias Response = SampleResponse
var url: URL = URL(string: "hghjg")!
var httpsMethod: HTTPSMethod = .GET
}
var apiManger2 = APIManager(https: HTTPSClient(), decodeData: DecodeData())
apiManger2.getData(request: SampleRequest()) { result in
switch result {
case .success(let response):
print("Success: \(response.message)")
case .failure(let error):
print("Error: \(error)")
}
}