I've been implementing MVVM in Swift. I've looked at several implementations, many of which violate some aspects of MVVM and wanted to have a go with my own version that contains a Web request service.
View:
class BreachView: UIView {
    var nameLabel = UILabel()
    public override init(frame: CGRect) {
        let labelframe = CGRect(x: 0, y: 50, width: frame.width, height: 20)
        nameLabel.frame = labelframe
        nameLabel.backgroundColor = .gray
        super.init(frame: frame)
        self.addSubview(nameLabel)
        backgroundColor = .red
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
ViewController:
class ViewController: UIViewController {
    var breachesViewModel: BreachViewModelType!
    var breachView : BreachView?
    // to be called during testing
    init(viewModel: BreachViewModelType) {
        breachesViewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    // required when called from storyboard
    required init?(coder aDecoder: NSCoder) {
        breachesViewModel = BreachViewModel()
        super.init(coder: aDecoder)
    }
    override func viewDidLoad() {
        super.viewDidLoad()       
        breachesViewModel.fetchData{ [weak self] breaches in
            guard let self = self else {return}
            DispatchQueue.main.async {
                self.updateUI()
            }
        }
    }
    func updateUI() {
        breachView = BreachView(frame: view.frame)
        breachesViewModel.configure(breachView!, number: 3)
        view.addSubview(breachView!)
    }
}
Protocol for dependency injection:
protocol BreachViewModelType {
    func fetchData(completion: @escaping ([BreachModel]) -> Void)
    func configure (_ view: BreachView, number index: Int)
}
ViewModel:
class BreachViewModel : BreachViewModelType {
    var breaches = [BreachModel]()
    init() {
        // add init for ClosureHTTPManager here, to allow it to be teestable in the future
    }
    func fetchData(completion: @escaping ([BreachModel]) -> Void) {
        ClosureHTTPManager.shared.get(urlString: baseUrl + breachesExtensionURL, completionBlock: { [weak self] result in
            guard let self = self else {return}
            switch result {
            case .failure(let error):
                print ("failure", error)
            case .success(let dta) :
                let decoder = JSONDecoder()
                do
                {
                    self.breaches = try decoder.decode([BreachModel].self, from: dta)
                    completion(try decoder.decode([BreachModel].self, from: dta))
                } catch {
                    // deal with error from JSON decoding!
                }
            }            
        })
    }
    func numberItemsToDisplay() -> Int {
        return breaches.count
    }
    func configure (_ view: BreachView, number index: Int) {
        // set the name and data in the view
        view.nameLabel.text = breaches[index].name
    }
}
and HTTP manager
class ClosureHTTPManager {
    static let shared: ClosureHTTPManager = ClosureHTTPManager()
    enum HTTPError: Error {
        case invalidURL
        case invalidResponse(Data?, URLResponse?)
    }
    public func get(urlString: String, completionBlock: @escaping (Result<Data, Error>) -> Void) {
        guard let url = URL(string: urlString) else {
            completionBlock(.failure(HTTPError.invalidURL))
            return
        }
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard error == nil else {
                completionBlock(.failure(error!))
                return
            }
            guard
                let responseData = data,
                let httpResponse = response as? HTTPURLResponse,
                200 ..< 300 ~= httpResponse.statusCode else {
                    completionBlock(.failure(HTTPError.invalidResponse(data, response)))
                    return
            }
            completionBlock(.success(responseData))
        }
        task.resume()
    }
}
Calling the API from
let baseUrl : String = "https://haveibeenpwned.com/api/v2"
let breachesExtensionURL : String = "/breaches"
Any comments on whether the implementation conforms to MVVM or not, typos, changes etc. are appreciated.
Git link: https://github.com/stevencurtis/MVVMWithNetworkService



viewDidLoadis only called once. Maybe you’re confusing it withviewDidAppear? \$\endgroup\$viewDidLoadcould be called multiple times. But it hasn’t been that way for a while. SeeviewDidUnload. If you ever see it called multiple times, nowadays, you have some other serious bug in your project. \$\endgroup\$