티스토리 뷰

swift/문법

17. Swift 문법 - Property

DevBee 2020. 10. 23. 16:22

1. Stored Properties

저장 속성은 형식 내부에 변수와 상수를 선언한 것입니다. 저장 속성은 클래스와 구조체에서 선언할 수 있으며 인스턴스에 속한 속성입니다. 따라서 인스턴스가 생성될 때마다 새로운 저장 공간이 생성됩니다. 바꿔 말하면 속성에 저장되는 값은 인스턴스마다 달라집니다.

 

문법을 살펴보면 다음과 같습니다.

var name: Type = DefaultValue
let name: Type = DefaultValue

var 키워드로 선언한 저장 속성은 Variable Stored Property 로 값을 나중에도 변경할 수 있습니다. let 키워드로 선언한 저장 속성은 Constant Stored Property 로 값을 초기화 하면 나중에 변경이 불가합니다.

기본 값을 지정하면 형식 추론을 통해 타입을 생략할 수 있고, 기본 값을 생략한다면 타입을 반드시 명시해야 합니다.

 

클래스를 생성하고 저장 속성을 선언해 보겠습니다.

class Person {
    let name: String = "John Doe"
    var age: Int = 30
}

 

새로운 인스턴스를 생성하고 속성을 확인해 보겠습니다. 속성을 확인하는 방법은 다음과 같습니다. 아래와 같은 방식은 Explicit Member Expression 이라고 합니다.

// 속성 값 가져오기
instanceName.propertyName

// 속성 값 변경하기
instanceName.propertyName = NewValue
let p = Person()
p.name  // 결과: "John Doe"
p.age   // 결과: 30

p.age = 35
// p.name = "New Name" - 에러: 상수로 선언된 저장 변수는 변경할 수 없습니다.

 

위에서 생성한 클래스를 구조체로 변경해보겠습니다.

struct Person {
    let name: String = "John Doe"
    var age: Int = 30
}

let p = Person()
p.name  // 결과: "John Doe"
p.age   // 결과: 30

p.age = 35 - 에러

 

age 저장 속성이 변수로 선언되어 있지만 해당 값을 변경하려는 코드에서 에러가 발생합니다. 값 형식인 구조체를 상수에 저장하는 경우 구조체에 포함된 모든 속성이 상수가 됩니다. 따라서 구조체로 선언된 값의 저장 속성을 변경하려는 경우 구조체를 변수에 저장해야 합니다.

var p = Person()
p.age = 35

 

저장 속성 중 Lazy Stored Properties (지연 저장 속성)이라는 것이 있습니다. 이는 인스턴스가 초기화 되는 시점에 속성이 초기화되는 것이 아니라 속성에 처음 접근하는 시점에 속성이 초기화됩니다. (초기화가 지연됨)

 

지연 저장 속성의 문법은 다음과 같습니다.

lazy var name: Type = DefaultValue

 

지연 저장 속성에는 몇가지 제한 사항이 있습니다.

1. 지연 속성은 인스턴스가 초기화된 이후에 개별적으로 초기화되기 때문에 항상 변수 저장 속성으로 지정해야 합니다. 즉, lazy let 은 불가능합니다.

2. 생성자에서 초기화 하지 않기 때문에 선언 시점에 기본값을 저장해야 합니다.

 

간략한 사용 예제를 살펴보겠습니다.

struct Image {
    init() {
        print("new image")
    }
}

struct BlogPost {
    let title: String = "Title"
    let content: String = "Content"
    lazy var attachment: Image = Image()
    
    let date: Date = Date()
    
    // 저장 속성을 클로저로 초기화할 때 다른 속성에 접근해야 한다면
    // 지연 저장 속성으로 선언해야 합니다.
    lazy var formattedDate: String = {
       let f = DateFormatter()
        f.dateStyle = .long
        f.timeStyle = .medium
        return f.string(from: date)
    }()
}

var post = BlogPost()
// 속성 접근 시에 초기화가 진행됩니다. 따라서 아래 코드를 실행해야 "new image" 가 출력됩니다.
post.attachment

 

2. Computed Property

계산된 속성은 다른 속성을 기반으로 다른 속성이 결정되는 것을 의미합니다.

 

계산된 속성의 특징을 살펴보면 다음과 같습니다.

 

- 계산 속성은 메모리 공간을 가지지 않습니다.

 - 다른 속성에 저장된 값을 읽어 필요한 계산을 수행한 다음에 리턴하거나 속성으로 전달된 값을 다른 속성에 전달합니다.

 - 이런 특징 때문에 속성에 접근할 때마다 다른 값이 리턴될 수 있습니다. 따라서 항상 var 키워드로 선언해야 합니다.

 - 선언 시 기본값을 지정할 수 없습니다. 따라서 형식 추론을 사용할 수 없고 반드시 자료형을 명시적으로 표시해주어야 합니다.

 - 계산 속성은 클래스, 구조체, 열거형 모두에 추가할 수 있습니다.

 

계산된 속성의 문법은 다음과 같습니다.

var name: Type {
    get {
        statements
        return expr
    }
    set (name) {
        statements
    }
}

 

get(get Block / geter)은 속성 값을 읽을 때 실행됩니다. 반드시 return 키워드를 통해서 다른 속성에 있는 값을 리턴하거나 자료형과 동일한 값을 리턴해야 합니다.

set(set Block / setter)은 값을 저장할 때 실행됩니다. 문법에서 set 키워드 다음에 오는 name 은 파라미터 이름입니다. 속성에 저장할 값은 이 파라미터로 전달됩니다. set block 에서는 괄호와 파라미터 이름을 생략할 수 있는데 이 경우에는 newValue 라는 이름을 가진 기본 파라미터를 사용합니다.

 

계산된 속성이 선언된 클래스를 생성하고 사용하는 예제를 살펴보겠습니다.

class Person {
    var name: String
    var yearOfBirth: Int
    
    init(name: String, year: Int) {
        self.name = name
        self.yearOfBirth = year
    }
    
    var age: Int {
        get {
            let calendar = Calendar.current
            let now = Date()
            let year = calendar.component(.year, from: now)
            return year - yearOfBirth
        }
        set {
            let calendar = Calendar.current
            let now = Date()
            let year = calendar.component(.year, from: now)
            yearOfBirth = year - newValue
        }
    }
}

let p = Person(name: "John Doe", year: 2002)
p.age  // 결과: 18

p.age = 50
p.yearOfBirth  // 결과: 1970

 

위 예제에서는 나이를 지정하면 출생 년도가 변경됩니다. 출생 년도가 변경되는 것은 옳지 않기 때문에 set block 을 제거하도록 하겠습니다. 이렇게 get block 만 남은 경우 Read-Only Computed Properties 라고 하며 get 키워드와 {}를 생략해 간결한 계산된 속성을 선언할 수 있습니다.

// 문법
var name: Type {
    get {
        statements
        return expr
    }
}

var name: Type {
    statements
    return expr
}

// 위 예제의 age 속성을 변경하면 다음과 같습니다.
var age: Int {
    let calendar = Calendar.current
    let now = Date()
    let year = calendar.component(.year, from: now)
    return year - yearOfBirth
}

 

get block 없이 set block 만 쓰는 것은 문법적으로 허용되지 않습니다.

 

3. Property Observer

속성을 감시하는 역할을 하는 것입니다.

 

Property Observer 의 특징을 살펴보면 다음과 같습니다.

 

- 변수 저장 속성에 추가할 수 있습니다.

- 상수 저장 속성, 지연 속성, 계산 속성에는 추가할 수 없습니다. 하지만 아예 구현하지 못하는 것은 아닙니다. 지연 저장 속성의 경우

   초기화 코드에서 원하는 기능(속성 감시 등)을 구현하면 되고 계산 속성의 경우 set block 에서 필요한 기능을 구현하면 됩니다.

   또한, sub class 에서 계산 속성을 오버라이딩 하고 여기에 Property Observer 를 구현하는 것은 가능합니다.

 

문법을 살펴보면 다음과 같습니다.

var name: Type = DefaultValue {
    willSet(name) {
        statements
    }
    didSet(name) {
        statements
    }
}

 

willSet은 속성에 값이 저장되기 직전에 호출됩니다. 새로 저장되는 값은 파라미터로 전달되고 파라미터 이름을 생략하면 newValue 라는 이름의 기본 파라미터를 사용합니다.

didSet은 값이 저장된 직후에 호출됩니다. 이 시점에는 속성에 새로운 값이 저장되어 있습니다. 그래서 didSet block 에는 이전 값이 파라미터로 전달됩니다. 파라미터 이름을 생략하면 oldValue 라는 이름의 기본 파라미터를 사용합니다.

willSet block, didSet block 은 필수 요소가 아니지만 모든 block 을 생략하는 것은 불가능합니다. Property Observer 가 되기 위해서는 반드시 두 블록 중 하나는 구현해야 합니다.

 

간단한 예를 통해 Property Observer 를 살펴보겠습니다.

class Size {
    var width = 0.0 {
        willSet {
            print(width, "=>", newValue)
        }
        didSet {
            print(oldValue, "=>", width)
        }
    }
}

let s = Size()
s.width = 123

// 결과
0.0 => 123.0  // willSet 에서 프린트 된 결과입니다.
0.0 => 123.0  // didSet 에서 프린트 된 결과입니다.

 

4. Type Property

형식 속성은 형식 자체에 속한 속성입니다. 인스턴스마다 개별 공간이 생성되지 않고 모든 인스턴스가 공유하는 하나의 공간만 생성됩니다. 그리고 모든 인스턴스가 여기에 저장된 값을 공유합니다.

 

특징은 다음과 같습니다.

- 형식 속성은 class, structure, enumeration 모두에 추가할 수 있습니다.

- 저장 속성과 계산 속성을 형식 속성으로 선언할 수 있습니다. 각각 저장 형식 속성과 계산 형식 속성이라고 부릅니다.

 

먼저 저장 형식 속성의 문법부터 살펴보겠습니다.

static var name: Type = DefaultValue // Variable Stored Type Property 으로 값 변경 가능
static let name: Type = DefaultValue // Constant Stored Type Property 으로 값 변경 불가능

 

접근 방법은 인스턴스 명이 아닌 형식 이름을 통해 접근합니다.

TypeName.propertyName

 

 

형식 자체에는 기본값이 없기 때문에 기본값을 생략 할 수 없고 속성에 최초로 접근할 때 초기화 됩니다.

 

간단한 선언 및 사용 예를 살펴보겠습니다.

class Math {
    static let pi = 3.14
}

let m = Math()
// m.pi - 에러
Math.pi  // 3.14

 

다음으로 계산 형식 속성을 살펴보겠습니다. 문법은 다음과 같습니다.

static var name: Type {
    get {
        statements
        return expr
    }
    set(name) {
        statements
    }
}

// class 에서 제한적으로 사용되는 문법
class var name: Type {
    get {
        statements
        return expr
    }
    set(name) {
        statements
    }
}

 

간단한 예를 살펴보겠습니다.

enum Weekday: Int {
    case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
    
    static var today: Weekday {
        let cal = Calendar.current
        let today = Date()
        let weekday = cal.component(.weekday, from: today)
        return Weekday(rawValue: weekday)!
    }
}

Weekday.today

 

5. self & super

self 란, 현재 인스턴스에 접근하기 위해 사용하는 특별한 속성입니다. 특징을 살펴보면 다음과 같습니다.

 

- 직접 생성하는 것이 아닌 인스턴스에 자동으로 추가되는 속성입니다.

- self 는 인스턴스 멤버 내부에서 접근하면 해당 인스턴스에 접근합니다.

- 그리고 타입 멤버 내부에서 접근한다면 형식 자체에 접근합니다.

 

사용 방법을 살펴보면 다음과 같습니다.

self                   // 인스턴스 자체에 접근할 때 사용
self.propertyName      // 인스턴스 속성에 접근할 때 사용 (self. 생략 가능)
self.method()          // 인스턴스 메소드를 호출할 때 사용 (self. 생략 가능)
self[index]            // 서브 스크립트를 호출할 때 사용 (self. 생략 불가능)
self.init(parameters)  // 동일한 형식에 있는 다른 생성자를 호출할 때 사용합니다. (self. 생략 불가능)
class Size {
    var width = 0.0
    var height = 0.0
    
    func calcArea() -> Double {
        return width * height // self. 생략
    }
    
    var area: Double {
        return calcArea()  // self. 생략
    }
    
    func update(width: Double, height: Double) {
        // 파라미터 이름과 속성 이름이 같은 경우 self 키워드로 구분
        self.width = width
        self.height = height
    }
    
    func doSomething() {
        // 클로저에서 속성에 접근하는 경우 self 를 캡처해야 하기 때문에 생략 불가
        let c = { self.width * self.height }
    }
    
    static let unit = ""
    
    static func doSomething() {
        // 형식 메소드에서 인스턴스 속성에 접근하는 것은 불가능
        // self.width - 에러
        
        self.unit // self. 생략 가능
    }
}

 

정리하자면,

1. self 는 현재 인스턴스에 접근하기 위해 사용하는 특별한 속성입니다.

2. self 를 타입 멤버에서 사용하면 인스턴스가 아닌 형식 자체를 나타냅니다.

 

위 예제에서 클래스로 선언한 객체를 구조체로 변경하면 self 를 이용하여 더 쉽게 속성 값을 지정하는 것도 가능합니다.

struct SizeStruct {
    var width = 0.0
    var height = 0.0
    
    mutating func reset(value: Double) {
        // width = value
        // height = value
        // 위 작업을 self 를 사용해 구현
        self = SizeStruct(width: value, height: value)
    }
}

 

super 는 상속과 관련된 속성으로 클래스에서만 사용이 가능합니다. 클래스의 상속과 관계 있기 때문에 상속을 정리할 때 알아보겠습니다. 기본적인 사용 방법은 self 와 동일합니다.

super.propertyName
super.method()
super[index]
super.init(parameters)
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함