Swift - 프로퍼티

임성빈·2022년 3월 14일
0

Swift

목록 보기
10/26
post-thumbnail
post-custom-banner

프로퍼티는 클래스, 구조체, 열거형과 관련한 값이다. 프로퍼티의 종류에는 저장 프로퍼티와 계산 프로퍼티가 있다. 저장 프로퍼티는 말 그대로 값을 저장하고 있는 프로퍼티이고, 계산 프로퍼티는 값을 저장하고 있지 않고 특정하게 계산한 값을 반환해 주는 프로퍼티이다. 계산 프로퍼티는 클래스, 구조체, 열거형 모두에서 사용 가능하지만, 저장 프로퍼티는 클래스와 구조체에서만 사용 가능하다. 추가로 프로퍼티 옵저버를 정의해서 값이 변할 때마다 모니터링할 수 있다.


저장 프로퍼티

저장 프로퍼티는 위에서 설명한 대로 단훈히 값을 저장하고 있는 프로퍼티이다. 이 프로퍼티는 let 키워드를 이용해서 상수 혹은 var 키워드를 이용해서 변수로 선언해 사용할 수 있다.

struct FixedLengthRange {
	var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 범위 값은 0, 1, 2 이다.

rangeOfThreeItems.firstValue = 6
// 범위 값은 6, 7, 8 이다.

위 예제를 보면 firstValuelength 에 첫 값과 그 길이를 각각의 프로퍼티에 저장해 범위 값을 표현한다.

상수 구조체 인스턴스의 저장 프로퍼티

구조체를 상수로 선언하면 그 구조체 인스턴스의 프로퍼티를 변경할 수 없다.

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 범위 값은 0, 1, 2, 3 이다.

rangeOfFourItems.firstValue = 6
// 에러 발생!

반면에 구조체가 아니라 클래스에서는 let 으로 선언하더라도 클래스 인스턴스는 참조 타입이기 때문에 프로퍼티가 변경 가능하다.

지연 저장 프로퍼티

지연 저장 프로퍼티는 값이 처음으로 사용 되기 전에는 계산되지 않는 프로퍼티이다. 지연 저장 프로퍼티로 선언하기 위해서는 프로퍼티의 선언 앞에 lazy 키워드를 붙이면 된다.

지연 프로퍼티는 반드시 변수로 선언해야한다. 상수는 초기화가 되기전에 항상 값을 갖는 프로퍼티인데, 지연 프로퍼티는 처음 사용되기 전에는 값을 갖지 않기 때문이다.

지연 프로퍼티는 프로퍼티가 특정 요소에 의존적이어서 그 요소가 끝나기 전에 적절한 값을 알지 못하는 경우에 유용하다. 또 복잡한 계산이나 부하가 많이 걸리는 작업을 지연 프로퍼티로 선언해 사용하면 실제 사용되기 전에는 실행되지 않아서 인스턴스의 초기화 시점에 복잡한 계산을 피할 수 있다.

class DataImporter {
    /*
        DataImporter는 외부 파일에서 데이터를 가져오는 클래스
        이 클래스는 초기화 하는데 매우 많은 시간이 소요된다고 가정
     */
    var filename = "data.txt"
    // 데이터를 가져오는 기능의 구현이 이 부분에 구현돼 있다고 가정
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 데이터를 관리하는 기능이 이 부분에 구현돼 있다고 가정
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 인스턴스는 이 시점에 생성돼 있지 않다.

DataManager 라는 클래스를 선언하고 이 클래스는 데이터를 가져오는 DataImporter 클래스를 갖고 있다. 그리고 이 DataImporter 는 실제 디스크 파일에서 데이터를 가져오기 때문에 초기화할 때 많은 시간이 소요 된다. 그래서 이 클래스를 지연 프로퍼티로 선언한다. 이 프로퍼티는 코드에서 볼 수 있는 것처럼 DataManager 인스턴스 manager 를 생성하고 거기에 data 를 넣어도 그 시점에 DataImporter 인스턴스는 생성돼 있지 않다. 다시 말하면 지연 프로퍼티로 선언해 놓았기 때문에 실제 그 프로퍼티를 사용하기 전에는 복잡하고 시간이 오래 소요되는 연산을 할 필요가 없다는 것이다.

print(manager.importer.filename)
// the DataImporter 인스턴스가 생겅
// "data.txt" 파일 출력

manager.importer.filename 가 실행돼 실제 importer 프로퍼티에 처음 접근할 때 비로소 importer 인스턴스가 생성된다.


계산 프로퍼티

저장 프로퍼티뿐만 아니라 추가적으로 클래스, 구조체, 열거형은 계산된 프로퍼티를 선언할 수 있다. 이 계산 프로퍼티는 실제 값을 저장하고 있는 것이 아니라 getteroptional 한 setter 를 제공해 값을 탐색하고 간접적으로 다른 프로퍼티 값을 설정할 수 있는 방법을 제공한다.

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
// initialSquareCenter : (5.0, 5.0)

square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// "square.origin is now at (10.0, 10.0)" 출력

Setter 선언으 간략한 표현

앞의 코드에서는 Setter 의 인자 이름을 아래와 같이 set(newCenter) 라고 명시했지만, 만약 이렇게 (newCenter) 라고 인자 이름을 지정하지 않으면 인자 기본 이름인 newValue 를 사용할 수 있다.

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

위 코드에서 set 메소드 안에서 인자 이름을 지정하지 않았는데도 newValue.x , newValue.y 를 사용할 수 있는 것을 볼 수 있다.

읽기 전용 계산 프로퍼티

getter 만 있고 setter 를 제공하지 않는 계산 프로퍼티를 읽기 전용 계산 프로퍼티라고 한다. 즉, 읽기 전용 계산 프로퍼티는 반드시 반환값을 제공하고 다른 값을 지정할 수 없는 프로퍼티이다.

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// "the volume of fourByFiveByTwo is 40.0" 출력

위 코드는 volume 이라는 읽기 전용 계산 프로퍼티를 사용한 예시이다.


프로퍼티 옵저버

프로퍼티에는 새 값이 설정 될 때마다 이벤트를 감지할 수 있는 옵저버를 제공한다. 이 옵저버를 프로퍼티 옵저버라 하는데 프로퍼티 옵저버는 새 값이 이전 값과 같더라도 호출 된다. 프로퍼티 옵저버는 지연 저장 프로퍼티에서 사용할 수 없다. 서브클래스의 프로퍼티에 옵저버를 정의하는 것도 가능핟. 계산 프로퍼티는 setter 에서 값의 변화를 감지할 수 있기 때문에 옵저버를 정의할 필요가 없다. 프로퍼티에서는 다음 두가지 옵저버를 제공한다.

  • willSet : 값이 저장되기 바로 직전에 호출 됨
  • didSet : 새 값이 저장되고 난 직후에 호출 됨
    willSet 에서는 새 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 newVlaue 를 사용한다.
    didSet 에서는 바뀌기 전의 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 oldValue 를 사용한다.
class StepCounter {
	var totalSteps: Int = 0 {
    	willSet(newTotalSteps) {
        	print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
        	if totalSteps > oldValue {
        		print("Added \(totalSteps - oldValue)")
            }
        }
    }
}
let stepCounter = StepCount()
stepCounter.totalSteps = 200
// "About to set totalSteps to 200"
// "Added 200"
stepCounter.totalSteps = 360
// "About to set totalSteps to 360"
// "Added 160"
stepCounter.totalSteps = 896
// "About to set totalSteps to 896"
// "Added 536"

프로퍼티 옵저버 willSetdidSet 을 사용한 것을 볼 수 있다. willSet 에서는 새로운 값의 파라미터명으로 newTotalSteps 를 지정해서 사용했고, didSet 에서는 변하기 전의 값을 의미하는 파라미터명을 지정하지 않고 oldValue 라는 기본 파라미터명을 이용했다. 실행 결과 로그를 보시면 값이 변하기 전과 변하고 나서 호출된 로그를 보며 프로퍼티 값 설정시 willSetdidSet 이 호출되는 순서를 확인할 수 있다.


전역 변수와 지역 변수

앞서 소개한 계산 프로퍼티와 프로퍼티 옵저버 기능은 전역 변수와 지역 변수 모두에서 이용가능하다. 전역 변수란 함수, 메소드, 클로저 혹은 타입 컨텍스트 밖에 정의된 변수이고 지역 변수는 그 안에 선언된 변수를 말한다.

전역 상수와 변수는 지연 저장 프로퍼티와 같이 지연 계산된다. 하지만 지연 저장 프로퍼티와 다르게 lazy 키워드를 붙일 필요는 없다. 반면 지역 상수와 변수는 지연 계산될 수 없다.


타입 프로퍼티

인스턴스 프로퍼티는 특정 인스턴스에 속한 프로퍼티를 말한다. 이 프로퍼티는 새로운 인스턴스가 생성될 때마다 새로운 프로퍼티도 같이 생성된다. 타입 프로퍼티는 특정 타입에 속한 프로퍼티로 그 타입에 해당하는 단 하나의 프로퍼티만 생성된다. 이 타입 프로퍼티는 특정 타입의 모든 인스턴스에 공통으로 사용되는 값을 정의할 때 유용하다.

타입 프로퍼티 구문

타입 프로퍼티를 선언하기 위해서는 static 키워드를 사용한다. 클래스에서는 staticclass 이렇게 2가지 형태로 타입 프로퍼티를 선언할 수 있는데 두 가지 경우의 차이는 서브클래스에서 overriding 가능 여부이다. class 로 선언된 프로퍼티는 서브클래스에서 overriding 가능하다. 구조체, 열거형, 클래스에서의 타입 프로퍼티 선언의 예시는 아래와 같다.

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

타입 프로퍼티의 접근과 설정

인스턴스 프로퍼티와 마찬가지로 타입 프로퍼티도 점연산자로 프로퍼티의 값을 가져오고 할당할 수 있다.

print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
profile
iOS 앱개발
post-custom-banner

0개의 댓글