[Swift] Lazy Property, 메소드 오버라이딩, 타입 캐스팅, 생성자 위임 정리

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     // 이미 계산된 값을 반환


동작 흐름

  1. post를 만들 때 attachment는 초기화되지 않는다.
  2. attachment에 처음 접근할 때 Image()가 호출되어 메모리에 올라간다.
  3. 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 propertycomputed 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를 호출해야 합


© 2021. All rights reserved.