이번 주부터 한 가지 주제를 깊이 공부하고, 토론하는 시간을 갖기로 했다.
첫 번째 주제는 초기화.
초기화란 단순히 변수를 0으로 만드는 것이 아니라, 변수를 선언하고 처음으로 값을 할당하는 과정을 의미한다.
처음엔 저 말이 굉장히 받아들이기 힘들었다... '초기화' 또는 initialize 라는 단어와 저 의미가 개인적으로 매칭이 굉장히 안 됐음...
저 말뜻은 이제 이해는 했는데, 변수에 Default Value를 줘서 직접 초기화 하는 것과 init 생성자에서 초기화하는 것의 차이와 장단점이 궁금해졌다.
Swift에서 클래스를 사용할 때 변수 초기화 방법에는 Default Value를 주는 것과 생성자(init) 사용 두 가지 방식이 있다.
여러 예시를 통해 차이점을 알아보자.
class NetworkManager {
let session: URLSession
let baseURL: String
init(session: URLSession = .shared, baseURL: String) {
self.session = session
self.baseURL = baseURL
}
func fetchData(endpoint: String, completion: @escaping (Data?) -> Void) {
guard let url = URL(string: "\(baseURL)\(endpoint)") else { return }
let task = session.dataTask(with: url) { data, _, _ in
completion(data)
}
task.resume()
}
}
class NetworkManager {
let session = URLSession.shared
let baseURL = "https://api.example.com"
func fetchData(endpoint: String, completion: @escaping (Data?) -> Void) {
guard let url = URL(string: "\(baseURL)\(endpoint)") else { return }
let task = session.dataTask(with: url) { data, _, _ in
completion(data)
}
task.resume()
}
}
init을 사용하면 baseURL을 동적으로 설정할 수 있으며, URLSession을 외부에서 주입할 수 있어 유닛 테스트가 가능하다.
반면, 직접 초기화한 경우 baseURL이 고정되어 있어 변경할 수 없으며, session을 외부에서 조작하기 어려워 테스트가 힘들다.
class UserSession {
let userID: String
var isLoggedIn: Bool
init(userID: String, isLoggedIn: Bool = false) {
self.userID = userID
self.isLoggedIn = isLoggedIn
}
func login() {
self.isLoggedIn = true
}
func logout() {
self.isLoggedIn = false
}
}
class UserSession {
var userID = "guest"
var isLoggedIn = false
func login() {
self.isLoggedIn = true
}
func logout() {
self.isLoggedIn = false
}
}
init을 사용하면 여러 개의 UserSession 객체를 만들 때 각각 다른 userID를 가질 수 있다.
직접 초기화한 경우 userID가 "guest"로 고정되어 있어, 개별 유저별 세션을 관리하기 어렵다.
class LoginViewController: UIViewController {
let username: String
init(username: String) {
self.username = username
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
print("\(username) 님, 로그인 화면입니다!")
}
}
class LoginViewController: UIViewController {
var username = "Guest"
override func viewDidLoad() {
super.viewDidLoad()
print("\(username) 님, 로그인 화면입니다!")
}
}
init을 사용하면 LoginViewController(username: "John")과 같이 사용자 이름을 설정할 수 있다.
반면, 직접 초기화한 경우 username이 "Guest"로 고정되어 있어 모든 화면에서 같은 이름을 사용하게 된다.
위 예시들을 보면 생성자를 사용하지 않고 초기화했을 때의 공통적인 문제점을 발견할 수 있다.
물론 다음과 같이 추가적인 setter 메서드를 사용하면 변경할 수 있지만, 코드가 더 복잡해지고 유지보수성도 떨어진다.
class NetworkManager {
var baseURL = "https://api.example.com"
func updateBaseURL(newURL: String) {
self.baseURL = newURL
}
}
이렇게 하면 updateBaseURL(newURL:)
을 호출해서 변경할 수 있긴 하지만,
init 사용 | 직접 초기화 | |
---|---|---|
객체마다 다른 값 설정 가능? | 가능 | 불가능 |
유닛 테스트 가능? | 가능(의존성 주입) | 어려움 |
상속 시 초기화 순서 조절 가능? | 가능(super.init()) | 불가능 |
변경 가능한 기본값 제공 가능? | 가능(기본 매개변수) | 어려움 |
초기화 로직 추가 가능? | 가능 | 불가능 |