문제 발생
Flutter를 하다가 다시 iOS 네이티브를 공부하려고 돌아오면서 몇 가지 UI들에 대해 테스트를 해보고 있었다.
스토리보드를 사용하지 않고, 코드로만 UI 구성하기를 연습하고 싶어서 스토리보드를 없애고 하나씩 확인을 해보고 있었다.
그 중 버튼을 눌러서 다른 화면으로 넘어가는 것을 해보고 있었는데, 아래의 코드에서 오류가 나기 시작했다.
▼
class ViewController: UIViewController {
let testButton : UIButton = {
let button = UIButton()
button.setTitle("Button", for: .highlighted)
button.layer.cornerRadius = 20.0
button.backgroundColor = .orange
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(ViewController.self, action: #selector(tapped), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
self.view.backgroundColor = .white
addSubViews()
autoLayout()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
private func addSubViews() {
view.addSubview(testButton)
}
private func autoLayout() {
testButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
testButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
testButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
testButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
@objc func tapped () {
let vc = NewViewController()
self.present(vc, animated: true)
}
}
처음에는 스토리보드를 없애서 발생하는 문제라고 생각을 했다.
스토리보드가 있을 때는 잘 작동했다고 생각했는데, 스토리보드가 있는 프로젝트를 새로 만들어서 시험해보니 똑같이 동작하지 않는 것을 보고 착각했다고 생각했다.
버전에 따른 문제인지, 단순히 나의 착각인지 아직은 정확히 모르겠지만 우선은 이 현상에 대한 해결방법을 알아내는게 우선이라고 생각했다.
해결방법
해결방법은 의외로 간단했다.
위의 버튼 코드에서 addTarget의 target 매개변수를 ViewController.self
에서 self
로 바꾸는 것이다. ▼
let testButton : UIButton = {
let button = UIButton()
button.setTitle("Button", for: .highlighted)
button.layer.cornerRadius = 20.0
button.backgroundColor = .orange
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
return button
}()
이렇게하면 의도한 대로 고양이 그림이 나오는 모달 페이지가 나온게 된다.
고쳐진 것은 좋으나 너무나도 허무한 느낌도 있다.
ViewController.self
에서 self
로 바꾼 것 만으로도 해결이 됐다는 것이 조금 허무하기도 하고, 둘이 같은 self
인데 왜 해결이 된 건지 이해가 가지 않는 것도 있다.
그렇다면 왜 self
로 바꾼 것 만으로 해결이 된 것일까?
ViewController.self와 self의 차이
해결이 된 이유는 둘이 같은 self
가 아니기 때문이다.
우선 이 둘의 차이에 대해 알기 위해서는 Swift의 MetaType
에 대해 알고 오는 것이 좋다. ▼
Xcode에서 문제가 없다고 말 하기도 했고, ViewController.self
대신에 self
를 넣으면 Warning이 나오며 권장하지도 않는다. ▼
"왜 이렇게 Warning 메세지를 띄우며 권장하지 않는 것일까?"
이는 우리가 self
를 사용하는 곳이 클로저 안이기 때문이다.
클로저 안에서 self
를 사용할 때는, 종종 메모리 누수를 방지하기 위해 weak self
또는 unowned self
를 사용하는 것이 일반적이다.
클로저 안에서 self
를 캡처하는 경우에는, 해당 클로저가 self
에 대한 강한 참조(strong reference)를 가지게 된다.
이로 인해, 의도하지 않은 메모리 누수가 발생할 수 있고, 이를 weak self
또는 unowned self
를 사용하여 이러한 문제를 완화할 수 있다.
아래와 같이 코드를 바꾸면 약한 참조를 가지게 되며, 더 이상 Warning 메세지를 띄우지 않는다. ▼
lazy var testButton: UIButton = {
[weak self] in
let button = UIButton()
button.setTitle("Button", for: .highlighted)
button.layer.cornerRadius = 20.0
button.backgroundColor = .orange
button.translatesAutoresizingMaskIntoConstraints = false
if let self = self {
button.addTarget(self, action: #selector(self.tapped), for: .touchUpInside)
}
return button
}()
Weak Self
lazy var testButton: UIButton = {
[unowned self] in
let button = UIButton()
button.setTitle("Button", for: .highlighted)
button.layer.cornerRadius = 20.0
button.backgroundColor = .orange
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(self.tapped), for: .touchUpInside)
return button
}()
unowned self
버튼 하나 만드는데 꽤나 생각해야될게 많아지지만, 이렇게 하면 메모리 누수에 대한 걱정이 줄어든다.
마치며
아마 이는 버튼 뿐만 아니라 addTarget을 가지는 모든 UI 컴포넌트들에 해당되는 문제일 것이다.
필자는 간단한 버튼을 만들다가 이런 문제를 마주해서 다행이라고 생각한다.
이런 문제를 마주하지 않고 (아마 그런 일은 거의 없겠지만…) 나중에 더 어려운 코드를 짜다가 이런 문제를 마주하면 고치는데 꽤나 오랜 시간이 들 것이라고 생각된다.
addTarget의 문제를 해결하면서 많은 것을 알게 되어 정말 다행이다.
'Develop > iOS' 카테고리의 다른 글
[SWIFT] 기본 연산자들 (0) | 2024.02.16 |
---|---|
[SWIFT] Swift 문법의 기초 (0) | 2024.02.15 |
[iOS][Swift] Swift의 메모리 관리 : ARC 2 (0) | 2024.02.11 |
[iOS][Swift] 클로져 캡쳐에 대한 이해 (0) | 2024.02.11 |
[iOS][Swift] Two Phase Initialization (0) | 2024.02.09 |