이번 프로젝트는 네트워크 통신을 이용해서 서버에서 랜덤 포켓몬 이미지를 불러오고 , 연락처를 저장하는 앱 개발을 수행했습니다.
처음 프로젝트를 시작했을때, 패턴 도입을 하기로 결정을 했습니다. 어떤 패턴을 사용해야할지 고민을 하다가, 포켓몬 연락처 앱이 소규모 프로젝트라는 점을 고려해 코드 가독성과 유지보수를 용이하게 하기 위해 MVC 패턴을 도입하기로 결정했습니다.
또한, 코드를 효율적으로 구조화하고 가독성을 높이는 데 유리하다는 이론을 바탕으로, 이번 프로젝트를 통해 직접 적용하며 성장의 기회로 삼고자 했습니다.
고민 끝에 MVC 패턴을 도입하기로 했지만, 역시나 적용 과정에서 예상하지 못한 문제들이 계속 발생했습니다. 처음으로 직접 판단을 내려야 하는 상황이 많았고, 작은 결정들로 인해 스노우볼이 굴러가 프로젝트 전체에 영향을 미칠 수 있다는 부담감을 느꼈습니다.
MVC 패턴에 맞게 폴더를 정리하며 Model, View, Controller를 나누었지만, 처음엔 제대로 설계하지 못해 파일 이동 과정에서 경로 오류가 발생했습니다.
🚨 문제
Build input file cannot be found
오류로 빌드가 중단 됨. 특히 Info.plist의 경로가 꼬이면서Multiple commands produce
라는 빌드 에러가 이어짐.✅ 해결
파일을 하나하나 이동 시키고,
Xcode의 Build Settings > Packaging > Info.plist File 을 찾아, 올바른 경로를 설정함.
MVC 패턴을 따르기 위해 MainView와 MainViewController를 분리했지만, view와 컨트롤러의 연결이 실패하여 빈 화면만 출력되었습니다.
🚨 문제
MainViewController에서 view를 불러오지 못해 UI가 표시되지 않음.
✅ 해결
loadView()
메서드로 뷰를 명시적으로 지정함.override func loadView() { view = mainView }
진짜진짜 개큰실수...😭
이후, view가 올바르게 연결되는 걸 확인하며 MVC 패턴을 제대로 활용하기 시작할 수 있었습니다.
랜덤 프로필 이미지를 생성하기 위해 Pokemon API를 호출하는 작업에서 어려움을 겪었습니다. 특히, API 모델과 네트워크 작업에 대한 이해가 부족해 시작조차 어려웠던 점이 가장 큰 문제였습니다. 비동기 작업과 옵셔널 처리라는 개념이 익숙하지 않아 코드를 작성하는 과정에서 막막함을 느꼈습니다.
튜터님의 도움으로 문제를 해결했지만, 근본적으로는 API 모델에 대한 이해가 부족했던 점이 가장 큰 원인이었습니다. 이를 계기로 네트워크 요청의 기본 개념과 옵셔널 처리의 중요성을 깊이 학습해야겠단 생각이 들었습니다. 앞으로는 비슷한 작업에서 스스로 코드를 작성할 수 있도록..!
이 과정에서 해결해야했던 문제 중 하나를 남깁니다.. 아직 이해하지 못한 에러는 제 메모장에
🚨 문제
API 호출 시 URL이 Optional(520)과 같은 형식으로 처리되며 잘못된 값이 전달됨.
✅ 해결
옵셔널 해제를 위해
guard let
을 활용함.guard let randomNumber = (1...1000).randomElement(), let url = URL(string: "https://pokeapi.co/api/v2/pokemon/\(randomNumber)") else { return }
연락처 데이터를 영구 저장하기 위해 UserDefaults를 사용했지만, 저장한 데이터가 UITableView에 즉시 반영되지 않는 문제가 있었습니다. UserDefaults에 데이터를 저장한 후, UITableView의 데이터 소스 갱신이 누락되었기 때문이었습니다.
🚨 문제
저장한 데이터를 즉시 표시하지 못함.
✅ 해결
viewWillAppear
메서드에서 데이터를 다시 로드하도록 구현해 문제를 해결함.viewWillAppear
는 화면이 나타날 때마다 호출돼서, 데이터를 갱신하기에 적합함.override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) loadContacts() }
이 과정에서 ViewController 생명주기에 대한 이해가 부족했던 점을 인지했으며, 생명주기 메서드의 역할과 활용 방법을 추가적으로 학습하기로 했습니다. 특히, viewDidLoad
와 viewWillAppear
의 차이점을 명확히 이해해야겠다는 생각을 했습니다.
Model
, View
, Controller
로 구분하여 각각의 역할을 분리함으로써 유지보수성과 가독성을 향상시키는 경험을 했습니다. NetworkManager
를 도입하여 API 호출 관련 코드를 별도의 클래스로 분리했습니다. fetchData<T: Decodable>
메서드가 다양한 데이터 모델에 유연하게 대응할 수 있도록 설계되었다는 점을 이해하고, 이를 통해 코드의 재사용성과 유지보수성을 높일 수 있다는 점을 배웠습니다.class NetworkManager {
func fetchData<T: Decodable>(url: URL, completion: @escaping (T?) -> Void) {
let session = URLSession(configuration: .default)
session.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
completion(decodedData)
} catch {
completion(nil)
}
}.resume()
}
}
API 호출 로직이 뷰 컨트롤러에서 분리되어 MVC 패턴의 원칙을 지킬 수 있었습니다.
재사용성과 테스트 가능성을 모두 확보할 수 있었던 점이 큰 장점으로 느껴졌습니다. 앞으로 다른 프로젝트에서 체험해볼 기회가 있으면 좋겠습니다.
View
와 ViewController
의 역할 분리MainViewController
와 MainView
를 분리하여 뷰와 컨트롤러의 역할을 명확히 했습니다.
MainViewController
는 데이터 관리와 로직에 집중하도록 하고,MainView
는 UI 구성 및 레이아웃 관리에만 집중하도록 구현했습니다.역할 분리가 명확해짐에 따라 코드 가독성이 향상되었고, 유지보수가 쉬워졌습니다.
ContactManager
를 통해 UserDefaults
에 데이터를 저장하고 불러오는 기능을 구현했습니다.
func saveContacts(name: String, phone: String, profileImage: Data?) {
var contacts = fetchContacts()
let newContact = Contact(name: name, phone: phone, profileImage: profileImage)
contacts.append(newContact)
if let encodedData = try? JSONEncoder().encode(contacts) {
UserDefaults.standard.set(encodedData, forKey: "contacts")
}
}
간단한 데이터 저장 방식이지만, UserDefaults를 활용하여 영구 데이터를 관리해보는 경험을 할 수 있었습니다. CoreData 혹은 이전에 사용해 본 다른 DB로 확장해보고 싶은 생각이 들었습니다.
이번 프로젝트를 통해 아키텍처 패턴의 힘을 느꼈습니다.
단순히 코드 가독성을 높이는 데 그치지 않고, 문제가 생겼을 때 원인을 명확히 파악하고 해결할 수 있는 기반이 되었다고 생각합니다. 앞으로도 이런 과정들을 반복하며 더욱 견고하고 확장성 있는 코드를 작성하고 싶습니다. 😊
MVC 뿌셔뿌셔!!