fix를 누르면 self -> ViewController.self로 고쳐주는 데 이는 해결법이 아니다.
에러는 사라지지만 의도한 동작을 하지않는다.
버튼을 let
이 아닌 lazy var
로 선언하여 해당 프로퍼티에 처음 접근할 때 초기화를 해주던가, button.addTarget...라인을 viewDidLoad
안에 작성하여 뷰가 완전히 로드된 후에 접근하도록 하는 것이다.
addTarget은 화면이 로드된 이후에 읽히도록 해야한다.
addTarget
과self
참조
addTarget
을 뷰가 로드된 이후에 작성해야 하는 이유는 self
참조가 안전하게 초기화된 후에야 사용할 수 있기 때문이다. 클로저 내부에서 self
를 참조해야 할 때 반드시 알고있어야할 개념이다.
UIView
나 UIButton
과 같은 UI 요소들은 뷰 컨트롤러의 생명주기에서 뷰가 로드된 후에 안전하게 설정할 수 있다.
초기화 시점과 lazy var
lazy var로 선언된 프로퍼티는 해당 프로퍼티가 처음 접근될 때 초기화된다. lazy키워드로 인해 초기화가 제대로 된 이후에 접근하므로 self 참조가 강한 참조와 관련된 경고나 에러가 발생시키지 않는다.
import UIKit
import SnapKit
class ViewController: UIViewController {
private let button: UIButton = {
let button = UIButton()
button.setTitle("저장", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .gray
button.titleLabel?.font = .boldSystemFont(ofSize: 30)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
private func configureUI() {
[button].forEach { view.addSubview($0) }
view.backgroundColor = .white
button.snp.makeConstraints {
$0.top.equalTo(textView.snp.bottom).offset(50)
$0.width.equalTo(60)
$0.height.equalTo(40)
$0.centerX.equalToSuperview()
}
}
@objc private func buttonTapped() {
UserDefaults.standard.set(textView.text, forKey: "memo")
print("ok")
}
}
여기서 "self"는 이 화면을 나타내는 ViewController
클래스의 인스턴스를 의미한다. 클로저나 함수내에서 self를 사용할 때 이를 캡쳐하여 해당 블록이 실행될 때 self에 접근한다.
문제는 버튼을 생성할 때, 버튼이 눌렸을 때 실행될 동작을 지정하는 코드에서 발생한다.
private let button: UIButton = {
let button = UIButton()
button.setTitle("저장", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .gray
button.titleLabel?.font = .boldSystemFont(ofSize: 30)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
return button
}()
여기서 button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
부분을 보면, 버튼이 눌렸을 때 buttonTapped
라는 함수를 호출하게 되어있다.
그런데 이 코드가 버튼을 초기화하는 동안 실행되기 때문에, 그 시점에 self
(ViewController
인스턴스)가 완전히 초기화되지 않았을 수 있다.
그래서 안전하게 버튼의 동작을 설정하려면, 버튼을 초기화할 때가 아니라, 화면이 다 로드된 이후로 설정해야 하는 것이다.
lazy var
를 사용해야 하는 경우: lazy var
는 해당 프로퍼티가 처음 접근될 때 초기화되는 특징을 갖고있다.
초기화 시점에 값을 알 수 없을 경우, 필요할 때 초기화하는 것이 좋다
객체가 완전히 초기화된 후에 설정되어야 하는 경우, 예를 들어 지금처럼 self
를 참조해야 하는 경우
비싼 연산이 필요한 경우:
객체 간의 강한 참조 순환을 피해야 하는 경우:
self
를 사용해야 하는 경우, self
가 초기화된 후에 접근하므로 lazy var
가 적합하다.lazy var expensiveObject: SomeClass = {
let instance = SomeClass()
instance.configureExpensiveResources()
return instance
}()
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("저장", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .gray
button.titleLabel?.font = .boldSystemFont(ofSize: 30)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
return button
}()
즉시 초기화 가능 여부:
let
을 사용.lazy var
를 사용초기화 비용:
let
을 사용lazy var
를 사용클로저에서 self
사용 여부:
self
를 참조해야 한다면 lazy var
를 사용let
: 값이 변경되지 않고 초기화 시점에 모든 정보를 사용할 수 있을 때 사용lazy var
: 초기화 시점에 값을 설정할 수 없거나, 나중에 필요할 때 초기화되는 것이 더 효율적일 때 사용