UIKit에서 UI를 그리는 방법은 두가지가 존재합니다. Storyboard를 이용하는 방식과 Codebase로 작성하는 방법이 있습니다.
저는 협업시 충돌, 유지보수 어려움등의 이유로 Codebase로 UI를 구현하는것을 선호합니다.
그럼 오늘 주제인 Codebase로 UI를 구현할때 UIView인스턴스를 생성해야 하는데 이때 lazy를 사용하는 이유 연산프로퍼티는 안되는 이유에 대해 알아보겠습니다. 추가로 UIView 인스턴스를 생성할 때 도움을 주는 라이브러리에 대해서도 소개하겠습니다.
연산프로퍼티로 생성하면 안되는 이유
그럼 일단 간단하게 UILabel하나를 연산프로퍼티를 통해 정의하고 AutoLayout을 설정해주는 코드를 작성하고 실행해 보겠습니다.
import UIKit
var label: UILabel {
return UILabel()
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: label,
attribute: .centerX,
relatedBy: .equal,
toItem: self.view,
attribute: .centerX,
multiplier: 1,
constant: 0).isActive = true
}
}
그러면 contraint를 설정해주는 과정에서 Crash가 발생합니다.
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
*** -[UILabel nsli_layoutEngine]: message sent to deallocated instance 0x7fec6fd87ce0
에러메세지는 이렇게 출력되는데 layout을 잡는 과정에서 instance가 존재하지 않아 생기는 문제입니다.
그럼 이렇게 되는 이유가 무엇이냐 알아보면 연산프로퍼티(Computed property)는 기본적으로 사용하는 시점에 클로저 내부 구문이 실행됩니다. 즉 addSubView하는 시점의 인스턴스와 constraint를 설정하는 시점의 인스턴스가 다르기 때문에 해당 문제가 발생합니다.
사진과 같이 print문을 출력할때마다 주소값이 달라지는걸 확인가능합니다.
이번에는 저장프로퍼티로 UILabel인스턴스를 생성하고 실행해보겠습니다.
import UIKit
var label = UILabel()
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: label,
attribute: .centerX,
relatedBy: .equal,
toItem: self.view,
attribute: .centerX,
multiplier: 1,
constant: 0).isActive = true
}
}
아주 정상적으로 작동합니다. 한번 선언된 저장프로퍼티는 바꾸지 않는 이상 인스턴스가 변경될 일이 없기때문에 addSubview와 constraint 설정 시 동일한 인스턴스를 보고 있습니다.
보통 UIView인스턴스를 생성할때 lazy프로퍼티를 사용해서 생성하는데 lazy 변수는 연산프로퍼티가 아니라 저장프로퍼티입니다. 클로져가 존재해 착각하시는 분들이 계시는데 lazy변수는 사용하는 시점에 생성되는건 연산프로퍼티와 같지만 연산프로퍼티와 다르게 한번 생성된 인스턴스는 변하지 않습니다.
Then 라이브러리
https://github.com/devxoul/Then
제가 소개드릴 라이브러리는 Then입니다. 유명한 전수열님이 만드셨습니다!!
Then은 UIView 인스턴스를 생성할때 활용할 수 있습니다.
사용 전 후 코드로 비교해보겠습니다.
lazy var label_lazy: UILabel = {
let label = UILabel()
label.text = "hello"
label.font = .systemFont(ofSize: 10)
return label
}()
var label_then = UILabel().then {
$0.text = "hello"
$0.font = .systemFont(ofSize: 10)
}
후행클로져를 통해 간단하게 속성값을 제어할 수 있습니다.
저는 주로 Then라이브러리를 통해 인스턴스를 생성하고 SnapKit라이브러리를 통해 제약조건을 설정합니다.
SnapKit 에 대해서는 추후에 포스팅 하도록 하겠습니다. 감사합니다.
'iOS' 카테고리의 다른 글
[iOS] Tuist App Extension 추가하기 (0) | 2022.12.28 |
---|---|
[iOS] UIButton Image와 Title사이 간격 조절하기 (0) | 2022.12.27 |
[iOS] Google Sheets와 Script로 현지화 자동화하기 (0) | 2022.12.27 |
[iOS] iOS프로젝트에 Script 설정하기 (with Tuist) (0) | 2022.12.21 |
[iOS] Tuist 외부라이브러리 가져오기 - Tuist (3/4) (0) | 2022.11.29 |