[Swift] Lazy Property, 메소드 오버라이딩, 타입 캐스팅, 생성자 위임 정리
카테고리 : Development
태그: Swift · Lazy Property · 메소드 오버라이딩 · 타입 체크 · 타입 캐스팅 · Initializer Delegation · 생성자 위임 · Computed Property · 기초 문법
태그: Swift · Lazy Property · 메소드 오버라이딩 · 타입 체크 · 타입 캐스팅 · Initializer Delegation · 생성자 위임 · Computed Property · 기초 문법
- 1. Lazy Property
- lazy property
- 2. 메소드 오버라이딩 (Overriding)
- 3. 타입 체크와 타입 캐스팅 (type check & type casting)
- 4. Initializer Delegation (생성자 위임)
1. Lazy Property
개념
- lazy property는 “필요할 때까지 값을 초기화하지 않고, 처음 접근하는 순간 메모리에 할당되는 속성”을 의미한다.
- 초기에는 메모리를 차지하지 않고,
- 처음 사용될 때 생성된다.
- 반드시
var
로 선언해야 한다. (let
은 불가)
예제
struct Image {
init() {
print("new image")
}
}
struct BlogPost {
let title: String = "Title"
let content: String = "Contents"
lazy var attachment: Image = Image()
let date: Date = Date()
var formattedDate: String {
let f = DateFormatter()
f.dateStyle = .long
f.timeStyle = .medium
return f.string(from: date)
}
}
var post = BlogPost() // 아직 attachment는 초기화되지 않음
post.attachment // 이 순간 attachment가 초기화되며 "new image" 출력
post.formattedDate // 이미 계산된 값을 반환
동작 흐름
post
를 만들 때attachment
는 초기화되지 않는다.attachment
에 처음 접근할 때Image()
가 호출되어 메모리에 올라간다.formattedDate
는 호출할 때마다 계산된다.
요약
항목 | lazy var | 일반 var |
---|---|---|
초기화 시점 | 처음 사용할 때 | 인스턴스 생성 시점 |
메모리 사용 | 필요할 때 메모리 차지 | 항상 메모리 차지 |
선언 가능성 | var만 가능 | let / var 모두 가능 |
Lazy Property vs Computed Property
lazy property
- 초기화가 딜레이된다.
- 한 번만 생성된다 (생성된 이후에는 값이 고정된다).
- 저장된 값을 사용한다.
lazy var image = Image()
computed property
- 매번 계산된다.
- 저장된 값이 없다.
- 접근할 때마다 새로운 값을 계산해서 리턴한다.
var formattedDate: String {
let f = DateFormatter()
f.dateStyle = .long
f.timeStyle = .medium
return f.string(from: date)
}
lazy vs computed 요약
항목 | lazy property | computed property |
---|---|---|
값 저장 여부 | 저장한다 | 저장하지 않는다 |
초기화 시점 | 첫 접근 시 1회 | 접근할 때마다 계산 |
var/let 사용 가능성 | var만 가능 | var 또는 let 가능 (읽기 전용이면 let) |
2. 메소드 오버라이딩 (Overriding)
개념
- 오버라이딩(Overriding)이란, 상속받은 메소드, 프로퍼티, 서브스크립트의 구현을 하위 클래스에서 새롭게 정의하는 것을 뜻한다.
override
키워드를 반드시 사용해야 하며, 부모 클래스의 기능을 수정하거나 확장할 때 쓴다.
예제
class Figure {
var name = "Unknown"
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Circle: Figure {
var radius = 0.0
override func draw() {
print("draw 🔴")
super.draw() // 부모 클래스의 draw 호출
}
}
let c = Circle(name: "Circle")
c.draw()
// 출력
// draw 🔴
// draw Circle
동작 흐름
c.draw()
를 호출하면Circle
클래스의draw()
가 실행된다.Circle
안에서super.draw()
를 호출하면 부모 클래스(Figure)의draw()
도 실행된다.
사용 패턴
패턴 | 설명 |
---|---|
상위 구현 완전히 무시 | 부모의 메소드를 호출하지 않고, 자식 구현만 실행 |
상위 구현 호출 후 실행 | UIKit에서 흔히 사용하는 패턴 (ex. viewDidLoad ) |
현재 구현 후 상위 구현 호출 | 자식 동작 먼저 실행한 다음 부모 메소드 호출 |
super
를 호출하지 않으면 부모의 기능은 실행되지 않는다.self.draw()
를 잘못 호출하면 자기 자신을 계속 호출해서 무한 루프(재귀호출)가 발생할 수 있다.
3. 타입 체크와 타입 캐스팅 (type check & type casting)
개념
- 타입 체크:
is
연산자로 인스턴스가 특정 타입인지 확인한다. - 타입 캐스팅:
as
,as?
,as!
연산자로 타입 변환을 시도한다.
예제
swift
복사편집
class Animal {
func sound() {
print("Some sound")
}
}
class Dog: Animal {
func bark() {
print("멍멍!")
}
}
class Cat: Animal {
func meow() {
print("야옹~")
}
}
let animals: [Animal] = [Dog(), Cat(), Dog()]
// 타입 체크
for animal in animals {
if animal is Dog {
print("Dog 타입입니다")
} else if animal is Cat {
print("Cat 타입입니다")
}
}
// 타입 캐스팅
for animal in animals {
if let dog = animal as? Dog {
dog.bark() // 성공하면 Dog 기능 사용
} else if let cat = animal as? Cat {
cat.meow() // 성공하면 Cat 기능 사용
}
}
핵심 흐름 요약
구분 | 설명 |
---|---|
is | 타입 체크만 함 |
as? | 다운캐스팅 시도 (옵셔널 결과) |
as! | 강제 다운캐스팅 (성공 보장될 때만 사용) |
- 배열에 여러 타입(상속 관계)을 넣고, 타입에 따라 다른 동작을 할 수 있다.
- 다운캐스팅은 항상 옵셔널(
as?
)을 기본으로 사용해야 안전하다. - 강제 다운캐스팅(
as!
)은 실패 시 앱이 죽을 수 있으니 주의해야 한다.
4. Initializer Delegation (생성자 위임)
개념
- 자기 안에 있는 다른 initializer나 부모 클래스의 initializer를 호출해서 초기화를 “위임”하는 것
목적
- 모든 저장 프로퍼티를 안전하게 초기화
- 중복 코드를 줄이고, 코드를 깔끔하게 유지
규칙
구분 | 설명 |
---|---|
Designated Initializer | 모든 프로퍼티 직접 초기화하고, 슈퍼 클래스의 Designated Initializer 호출 |
Convenience Initializer | 같은 클래스 안의 다른 Initializer를 호출 (최종적으로 Designated로 연결) |
예제
struct Person {
let name: String
let age: Int
// 메인 생성자 (Designated Initializer)
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 편의 생성자 (Convenience Initializer)
init(name: String) {
self.init(name: name, age: 0) // 나이를 모를 때 기본값 0으로
}
}
// 사용
let p1 = Person(name: "John", age: 25)
let p2 = Person(name: "Jane")
클래스 예제
class Animal {
let species: String
// 메인 생성자
init(species: String) {
self.species = species
}
// 편의 생성자
convenience init() {
self.init(species: "Unknown")
}
}
class Dog: Animal {
let name: String
// 메인 생성자
init(name: String) {
self.name = name
super.init(species: "Dog")
}
// 편의 생성자
convenience init() {
self.init(name: "No Name")
}
}
// 사용
let dog1 = Dog(name: "Buddy")
let dog2 = Dog()
흐름 | 설명 |
---|---|
Delegate Across | 같은 클래스 안에서 다른 생성자 호출 |
Delegate Up | 상위 클래스 생성자 호출 |
Delegate Up : Designated Initializer는 반드시 슈퍼 클래스의 Designated Initializer를 호출해야 함
Delegate Across(1) : Convenience Initializer는 반드시 동일한 계층(클래스)의 initailizer를 호출해야 함
Delegate Across(2) : Convenience Initializer는 최종적으로 동일한 계층(클래스)의 Designated initialize를 호출해야 합 ㄴ