본문 바로가기

Swift 문법정리

1.2 Classes and Structures Property 정리

Classes and Structures Property

1.1 Property

저장 프로퍼티 
- 입력된 값을 저장하거나 저장된 값을 제공
- 상수 및 변수를 사용해 정의 가능
- 클래스와 구조체에서는 사용이 가능하나 열거형에서는 사용 불가

연산 프로퍼티
- 특정 연산을 통해 값을 만들어 제공
- 변수만 사용해서 정의 가능
- 클래스, 구조체, 열거형에서 모두 가능

 

속성은 클래스나 구조체 내에서 선언되고 사용된 변수 혹은 상수를 말합니다. 속성은 값에 대한 저장 여부를 기준으로 저장 프로퍼티와 연산 프로퍼티로 나눌 수 있습니다. 

 

저장, 연산 프로퍼티는 클래스나 구조체로 만들어진 인스턴스에 소속되어 값을 저장하거나 연산을 처리하는 역할을 하기 때문에 인스턴스의 생성 후 인스턴스를 통해 참조하거나 값을 할당해야 합니다. 이와 같이 인스턴스에 소속된 속성을 인스턴스 프로퍼티라고 합니다. 

일부 속성은 클래스와 구조체에 소속되어 값을 가지기 때문에 인스턴스를 생성하지 않아도 사용이 가능합니다. 이러한 속성을 타입 프로퍼티라고 합니다. 

1.2 Stored Property

저장 프로퍼티는 클래스 내에서 선언한 변수나 상수를 말합니다. 일반 변수나 상수처럼 선언할 때 초기값을 할당할 수 있으나 반드시 선언 시점에 초기값을 할당해야 하는 것은 아닙니다. 구조체의 경우 멤버 와이즈 구문처럼 초기화 구문에서 초기값을 설정해도 됩니다. 

하지만 클래스는 구조체와는 다르게 선언 시 초기값이 할당되지 않은 저장 프로퍼티는 옵셔널 타입으로 선언해야 합니다. 인스턴스 생성 시 값이 비어있을 경우 무조건 nil 값으로 초기화하기 때문입니다. 클래스를 초기화하는 방법은 다음과 같습니다.

 

class User {
    var name : String
    init () {
        self.name = ""
    }
}

 

init()은 초기화 구문으로 형태가 메서드와 비슷하여 초기화 메서드라고도 불립니다. 일반 메서드는 직접 호출되지만 인스턴스가 생성될 때 간접적으로 호출되는 경우가 대부분입니다. init 메서드 내부에 작성된 구문은 인스턴스가 생성될 때 실행됩니다. 

클래스에서 선언된 프로퍼티나 메서드의 경우는 self를 붙여줍니다. 

 

class User {
    var name : String?
}

class User {
    var name : String!
}

 

위 예시와 같이 옵셔널 타입으로 프로퍼티를 선언 하레 될 경우 초기화하지 않더라도 자동으로 초기화가 진행됩니다.

 

class User {
    var name : String = ""
}

 

위 예시는 선언과 동시에 빈 초기값을 입력해주는 것입니다. 

 

var 로 정의한 변수형 저장 프로퍼티 (맴버 변수)
let 로 정의한 상수형 저장 프로퍼티 (맴버 상수)

 

저장 프로퍼티는 var, let으로 저장한 변수형, 상수형 저장 프로퍼티로 나눌 수 있습니다. 두 프로퍼티는 변수와 상수의 성격과 똑같이 var로 정의한 멤버 변수는 수정이 가능하고 멤버 상수로 정의한 값을 변경 없이 그대로 유지됩니다. 

 

struct FixedLengthRange {
    var firstValue : Int
    let length : Int
}

let rangeOfFixedThreeItems = FixedLengthRange (firstValue : 0, length : 3)
rangeOfFixedThreeItems.firstValue = 6 //Error

struct FlexibleLengthRange {
    let firstValue : Int
    var length : Int
}

var rangeOfFlexibleThreeItems = FlexibleLengthRange (firstValue : 0, length : 3)
rangeOfFlexibleThreeItems.length = 9
rangeOfFlexibleThreeItems.firstValue = 1 // Error

 

앞에서 말했듯이 프로퍼티들은 변수와 상수의 성격을 그대로 받아 FlexibleLengthRange의 프로퍼티 상수 firstValue는 rangeOfFlexibleThreeItems.firstValue = 1을 통해 값을 1로 변경하려고 했지만 상수이기 때문에 오류가 납니다. 이는 프로퍼티뿐만 아니라 구조체 인스턴스를 할당할 경우에도 마찬가지입니다. 

FixedLengthRange의 프로퍼티 변수 firstValue는 rangeOfFixedThreeItems.firstValue = 6을 통해 값을 6으로 변경하려 했지만 오류가 납니다. 이는 FixedLengthRange의 인스턴스를 상수 rangeOfFixedThreeItems에 할당했기 때문에 변경 시 오류가 발생합니다. 

 

반면 클래스의 경우에는 클래스 인스턴스를 상수에 할당하더라도 클래스 내에서 변수로 선언한 저장 프로퍼티들을 수정할 수 있습니다. 이는 구조체와 클래스의 값 전달 방식의 차이로 구조체는 복사, 클래스는 참조에서 비롯됩니다.

1.2.1 Lazy Stored Property

class DataImporter {
    var fileName = "data.txt"
}

class DataManager {
    lazy var importer = DataImporter ()
    var data = [String] ()
}

let manager = DataManager ()

manager.data.append("Some data") // ["Some data"]
manager.data.append("Some more data") // ["Some data", "Some more data"]

print (manager.importer.fileName) // "data.txt"

 

일반적인 저장 프로퍼티는 클래스 인스턴스가 생성될 때 초기화되지만 지연 저장 프로퍼티는 초기화를 지연시킵니다. 클래스 인스턴스가 생성되더라도 지연 저장 프로퍼티는 선언만 되고 초기화는 하지 않다가 프로퍼티가 호출되면 초기화가 실행됩니다. 만약 지연 저장 프로퍼티에 클래스나 구조체 인스턴스가 대입된다면 프로퍼티 호출 전까지는 인스턴스는 초기화되지 않습니다. 

상수 manager 안에 DataManager 인스턴스를 넣고 클래스 DataManager 안에 지연 저장 프로퍼티 변수 importer안에 DataImporter 인스턴스를 넣었습니다. DataImporter 인스턴스는 지연 저장 프로퍼티 변수 importer 안에 들어있기 때문에 print (manager.importer.fileName)처럼 importer의 호출이 있을 때 초기화됩니다. 두 번째 호출부터는 다시 초기화되지 않고 처음 초기화된 값을 사용합니다.

1.3 Coumputed Property

연산 프로퍼티는 저장했던 값을 반환하지 않고 다른 프로터티의 값을 연산하여 값을 제공합니다. 연산 프로퍼티는 get구문과 set 구문을 사용하는데 get구문은 필수 요소이지만 set구문은 선택사항입니다. set구문이 생략되면 외부에서 연산 프로퍼티 값을 할당할 수 없고 내부적인 연산처리를 통해 값만 제공받는 읽기 전용 프로퍼티가 만들어집니다.

 

class/sturct/eunm 객체명 {
    var 프로퍼티명 : 타입 {
        get {
            연산 과정
            return 반환값
        }
        set (매개변수명) {
            연산 구문
    }
}

 

연산 프로퍼티는 클래스, 구조체, 열거형 내부에서만 사용이 가능합니다. 연산 프로퍼티는 다른 프로퍼티에 의존하거나 특정 연산을 통해 얻을 수 있는 값을 정의할 때 사용합니다. ex) 나이

 

struct UserInfo {
    var birth : Int!
    var thisYear : Int! {
        get {
            let df DateFormatter ()
            df.dateFormat = "yyyy"
            return Int(df.String(from:Date())
            }
        }
    var age : Int {
        get {
            return (self.thisYear - self.birth) + 1
        }
    }
}

let info = UserInfo(birth : 1980)
print(info.age)

 

위의 예시는 나이를 구하는 구조체로 연산 프로퍼티를 사용한 것을 볼 수 있습니다.

 

struct Rect {
    var originX : Double = 0.0, originY : Double = 0.0
    var sizeWidth : Double = 0.0, var sizeHeight : Double = 0.0
    
    var centerX : Double {
        get {
            return self.originX + (suzeWidth / 2)
        }
        set(newCenterX) {
            originX = newCenterX - (suzeWidth / 2)
        }
    }
    
    var centerY : Double {
        get {
            return self.originY + (self.sizeHeight /2)
        }
        set (newCenterY) {
            self.originY = newCenterY - (self.sizeHeight /2)
     }
}

var square = Rect(originX : 0.0, originY : 0.0, sizeWidth : 10.0, sizeHeight : 10.0)
print("square.centerX = \(square.centerX), square.centerY = \(square.centerY)")

// square.centerX - 5.0, square.centerY = 5.0

 

위 예시는 x, y의 중심 좌표를 구하는 구조체로 x, y의 중심 좌표는 x, y의 좌표와 가로 세로 길이에 의해 결정되기 때문에 연산 프로퍼티로 구조체를 만들었습니다. 

 

struct Position {
    var x : Double = 0.0
    var y : Double = 0.0
}

struct Size {
    var width : Double = 0.0
    var height : Double = 0.0
}

struct Rect {
    var origin = Position ()
    var size = Size ()
    var center : Position {
        get {
            let centerX = self.originX + (self.size.width / 2)
            let centerY = self.originY + (self.size.height / 2)
            return Position (x : centerX, y : centerY)
        }
        set(newCenter) {
            self.originX = newCenter.x - (size.width / 2)
            self.originY = newCenter.Y - (size.height / 2)
        }
    }
}

let p = Position (X : 0.0, Y : 0.0)
let s = Size (width : 10.0, height : 10.0)

var square = Rect(origin : p, size : s)
print("square.centerX = \(square.centerX), square.centerY = \(square.centerY)")
// square.centerX - 5.0, square.centerY = 5.0

1.4 Property Observer 

프로퍼티 옵서버는 프로퍼티의 값이 변경되면 호출되게 됩니다. 

 

willset - 프로퍼티의 값이 변경되지 직전에 호출되는 옵저버
didset - 프로퍼티의 값이 변경된 진후에 호출되는 옵저버

 

willset은 프로퍼티에 값을 대입하면 프로퍼티에 대입되기 직전에 willset 옵서버가 실행됩니다. 이때 프로퍼티에 대입되는 값이 옵서버의 실행 블록에 매개 상수 형식으로 전달됩니다. 단 전달된 값은 참조가 가능하지만 수정할 수는 없습니다. 값을 편리하게 다루기 위해 willSet 블록 내에 이름을 부여할 수 있지만 선택사항이며 이름을 부여하지 않을 때는 매개 상수 이름과 괄호를 생략하면 됩니다. 

생략할 경우 newValue라는 이름으로 전달됩니다. 

 

var <프로터피명> : 타입 [ = <초기값>] {
    willSet [(<인자명>)] {
        <프로퍼티 값이 변경되기 전에 실행할 내용>
    }
}

var <프로터피명> : 타입 [ = <초기값>] {
    didSet [(<인자명>)] {
        <프로퍼티 값이 변경ehls 후에 실행할 내용>
    }
}

 

[]는 생략이 가능한 부분임을 의미합니다. 옵저버 작성 시 대괄호는 표시하지 않습니다. 

프로퍼티에 값이 할당된 직 후 호출되는데 새로 할당된 값이 아닌 기존에 저장되어있던 값이 매개 상수 형태로 전달됩니다. didSet 구현 블록 내에서 사용할 수 있는 이름을 부여할 수 있지만 생략하더라도 oldValue라는 이름으로 전달됩니다. 

willSet, didSet은 선택적으로 사용이 가능합니다. 

 

struct Job {
    var income : Int = 0 {
        willSet(newIncome) {
            print("이번 달 월급은 \(newIncome)원 입니다.")
        }
        
        didSet {
            if income > oldValue {
                print("월급이 \(income - oldValue)원 증가하셨네요. 소득세가 상향될 예정입니다".)
            }else {
                print("월급이 삭감되어서 소득세는 그래도 입니다")

 

Job이라는 구조체에 willSet과 didSet을 설정하였습니다. 앞서 말했듯이 willSet은 매개 상수처럼 () 안에 willSet블록 내에서 쓸 수 있는 이름을 정의할 수 있습니다. didset의 경우 설정하지 않아 oldValue라는 값으로 나타내고 있습니다. 

 

var job = Job(income : 100000)
job.income = 200000

// "이번 달 월급은 200000원 입니다."
// "월급이 100000원 증가하셨네요. 소득세가 상향될 예정입니다."

 

처음 변수 job에 Job 인스턴스를 할당하고 income에 값 100000을 넣었습니다. 그 후 다시 200000원이라는 값을 넣었습니다. 새로 들어온 200000은 income이 200000으로 바뀌기 전에 willSet 구문을 실행시켜 200000을 newincome에 넣습니다. 그 후 income은 200000으로 변경되고 didSet 구문이 실행됩니다. income에는 200000으로 변경되었고 oldValue는 기존의 있던 값이 100000으로 200000 - 100000이 됩니다. 

1.5 Type Property

저장, 연산 프로퍼티는 클래스, 구조체 인스턴스를 생성한 후 인스턴스를 통해 참조할 수 있는 프로퍼티였습니다. 이들을 인스턴스 프로퍼티라고 부릅니다. 인스턴스에 관련된 값이 아닌 클래스, 구조체, 열거형과 같은 객체 자체에 관련된 값을 다루고 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장하는 프로퍼티를 타입 프로퍼티라고 부릅니다. 

이들은 클래스나 구조체 자체에 속하는 값이므로 인스턴스를 생성하지 않고 클래스나 구조체 자체에 저장하며 저장된 값은 모든 인스턴스가 공통으로 사용할 수 있습니다.

인스턴스 프로퍼티는 개별 인스턴스마다 다른 값을 저장할 수 있어 하나의 인스턴스에서 변경한 프로퍼티의 값은 인스턴스 내에서만 유지될 뿐 나머지 인스턴스에 영향을 미치지 않지만 타입 프로퍼티는 모든 인스턴스가 하나의 값을 공유합니다. 

 

static let/var 프로퍼티명 = 초기값

class let/var 프로퍼티명 : 타입 {
    get {
       return 반환값
    }
    set {
    }
}

 

타입 프로퍼티를 사용하기 위해서는 구조체, 클래스에서는 위의 예시처럼 static을 이용하여 설정할 수 있으며 저장, 연산 프로퍼티 상관없이 모두 사용이 가능합니다. 아래 예시는 클래스에서 연산 프로퍼티에만 사용이 가능합니다. 구조체, 저장 프로퍼티에는 사용할 수 없습니다. 

 

struct Foo {
    // 타입 저장 프로퍼티
    static var sFoo = "구조체 타입 프로퍼티 값"
    
    // 타입 연산 프로퍼티
    static var cFoo : Int {
        return 1
    }
}

class Boo {
    // 타입 저장 프로퍼티
    static var sFoo = "클래스 타입 프로퍼티값"
    
   // 타입 연산 프로퍼티
   static var cFoo : Int {
       return 10
   }
   
   //재정의가 가능한 타입 연산 프로퍼티
   class var oFoo : Int {
       return 100
   }
}
    

 

구조체 Foo, 클래스 Boo 각각 타입 프로퍼티가 선언되어 있습니다. 

 

print(Foo.sFoo)
// "구조체 타입 프로퍼티 값"

Foo.sFoo = "새로운 값"

print(Foo.sFoo)
//  "새로운 값"

print(Boo.sFoo)
// "클래스 타입 프로퍼티값"

print(Boo.cFoo)
// 10

 

타입 프로퍼티들은 별도의 인스턴스 생성 없이 사용이 가능합니다. 클래스나 구조체 자체에 점 구문을 이용하여 타입 프로퍼티를 참조하면 됩니다. 인스턴스에 속하지 않기 때문에 인스턴스를 생성한 다음 점 구문을 이용하여 프로퍼티를 읽으려 한다면 오류가 생성됩니다. 타입 프로퍼티는 클래스, 구조체, 열거형 자체와 함께 사용해야 합니다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Swift 문법정리' 카테고리의 다른 글

1.3 Classes and Structures Method정리  (0) 2020.05.21
1.1 Classes and Structures 정리  (0) 2020.05.13
Function 정리  (0) 2020.05.11
Collection Types 정리  (0) 2020.05.08
Optional 정리  (0) 2020.05.08