Introdução: Por que SOLID é importante para um desenvolvedor Swift
Todo desenvolvedor iOS mais cedo ou mais tarde enfrenta o problema do "código espaguete": controladores incham para milhares de linhas, alterar uma entidade quebra outras três, e os testes se tornam um pesadelo. Os princípios SOLID são cinco regras fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e testável em Swift.
Neste artigo, vamos analisar cada princípio SOLID com exemplos concretos do desenvolvimento iOS. Você aprenderá como aplicar esses princípios em projetos reais, evitando erros típicos. O material será útil tanto para desenvolvedores Junior quanto para profissionais experientes que desejam sistematizar seus conhecimentos.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Essência: Cada classe deve ter apenas um motivo para mudar. Em outras palavras, a classe deve ser responsável por apenas uma tarefa.
Problema: O ViewController "Divino"
Um erro típico é colocar toda a lógica no UIViewController. Vejamos um exemplo ruim:
class UserProfileViewController: UIViewController { @IBOutlet weak var nameLabel: UILabel! func loadUser() { // 1. Lógica de rede let url = URL(string: "https://api.example.com/user")! URLSession.shared.dataTask(with: url) { data, _, _ in // 2. Parsing JSON let user = try! JSONDecoder().decode(User.self, from: data!) // 3. Salvamento no Core Data self.saveToCoreData(user) // 4. Atualização da UI DispatchQueue.main.async { self.nameLabel.text = user.name } }.resume() } private func saveToCoreData(_ user: User) { /* ... */ }}Solução: Separação de Responsabilidades
Vamos dividir o código em componentes separados:
// 1. Serviço para trabalhar com redeclass UserNetworkService { func fetchUser(completion: @escaping (Result<User, Error>) -> Void) { let url = URL(string: "https://api.example.com/user")! URLSession.shared.dataTask(with: url) { data, _, error in if let data = data { do { let user = try JSONDecoder().decode(User.self, from: data) completion(.success(user)) } catch { completion(.failure(error)) } } else if let error = error { completion(.failure(error)) } }.resume() }}
// 2. Gerenciador de armazenamento de dadosclass UserStorageManager { func save(_ user: User) { // Lógica de salvamento no Core Data / Realm }}
// 3. ViewModel (ou Presenter)class UserViewModel { private let networkService = UserNetworkService() private let storageManager = UserStorageManager() var onUserUpdated: ((User) -> Void)? func loadUser() { networkService.fetchUser { [weak self] result in switch result { case .success(let user): self?.storageManager.save(user) self?.onUserUpdated?(user) case .failure(let error): print(error.localizedDescription) } } }}
// 4. ViewController agora apenas gerencia a UIclass UserProfileViewController: UIViewController { @IBOutlet weak var nameLabel: UILabel! private let viewModel = UserViewModel() override func viewDidLoad() { super.viewDidLoad() viewModel.onUserUpdated = { [weak self] user in DispatchQueue.main.async { self?.nameLabel.text = user.name } } viewModel.loadUser() }}Resultado: Cada classe tem um motivo para mudar. Testar e manter esse código é significativamente