Swift 정리 - Properties

김세영·2021년 12월 20일
0

Swift Doc 정리

목록 보기
9/17
post-thumbnail

프로퍼티는 클래스, 구조체, 열거형과 관련된 값을 뜻함
3가지 종류가 존재

Stored Properties

단순히 값을 저장하고 있는 프로퍼티

struct Point {
    let name: String
    var x, y: Int
    // name, x, y는 저장 프로퍼티
}

class Person {
    let name: String
    var age: Int
    // name, age는 저장 프로퍼티
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

구조체를 상수(let)로 선언한 경우 그 인스턴스의 프로퍼티를 변경할 수 없다.
하지만 클래스는 가능, 참조 타입이기 때문

let point1 = Point(name: "p1", x: 10, y: 3)
point1.x = 3 // Compile Error -> 'point1' is a 'let' constant

var point2 = Point(name: "p2", x: -5, y: 4)
point2.x = 3 // point2.x : 3

let kim = Person(name: "Kim", age: 23)
kim.age += 1 // kim.age : 24

var lee = Person(name: "Lee", age: 25)
lee.age += 1 // lee.age : 26

lazy

lazy 키워드로 지연 저장 프로퍼티를 선언할 수 있다.
값이 처음으로 사용되기 전에는 계산되지 않는 프로퍼티

  • 사용
    프로퍼티가 특정 요소에 의존적이어서 해당 요소가 끝나기 전에 값을 알지 못할 때
    복잡한 계산이나 부하가 많이 걸리는 작업을 인스턴스 초기화 시점에서 피하고 싶을 때

  • 무조건 var로 선언해야 한다.
    상수는 초기화가 되기 전에 값을 가져야 하는데,
    지연 저장 프로퍼티는 처음 실행되기 전까지는 값을 갖지 않기 때문

  • 스레드에 따른 초기화 횟수
    단일 스레드에서만 지연 프로퍼티가 실행되면 초기화는 한 번만 하지만,
    여러 스레드에서 사용되면 한 번만 한다고 보장하지 못한다.

class DiskReader {
    // 디스크에서 파일을 읽어오는 작업
    // 초기화하는데에 시간이 오래 걸린다고 가정
    func readDisk() {
        ...
    }
}
...
class FileManager {
    lazy var reader = DiskReader()
    var file = [String]()
}

let fileManager = FileManager()
fileManager.file.append("readme.txt")
fileManager.file.append("SceneDelegate.swift")
// 이 시점엔 FileManager의 DiskReader 인스턴스가 생성되어 있지 않음

fileManager.reader.readDisk()
// 이 때 DiskReader 인스턴스를 생성

Computed Properties

Computed Property는 실제 값을 저장하지 않고 getter와 setter를 제공하여
값을 가져오거나 간접적으로 다른 프로퍼티의 값을 설정할 수 있다.

  • 형태
...
var computedProperty: Type {
    get { }
    set(parameter = "newValue") { }
}
...
  • get

    • 해당 프로퍼티를 읽을 때 get의 코드 블럭을 실행하게 된다.

    • 이 코드 블럭은 프로퍼티 타입의 반환값을 제공해야 하며, 한 줄로 작성할 때는 return 키워드 생략 가능

    • 무조건 get은 포함해야 한다

  • set

    • 해당 프로퍼티에 값을 쓸 때 set의 코드 블럭을 실행하게 된다.

    • 형태에서 set(parameter) 형태로, 쓰여질 값이 parameter에 넘어오게 되는데
      set { } 형태로 인자 없이 쓸 경우, 쓰여질 값은 코드 블럭 안에서 기본 인자 이름인 newValue로 사용된다.

    • set은 제공하지 않아도 되며, set을 제공하지 않으면 get-only property가 된다.

Property Observers

프로퍼티는 새 값이 설정될 때마다 (set될 때 마다) 이벤트를 감지할 수 있는 옵저버를 제공

  • 서브클래스의 프로퍼티에 옵저버를 정의 가능

  • inout 파라미터에 프로퍼티를 넘기면 willSet, didSet이 항상 호출됨
    inout 파라미터는 항상 복사가 일어나기 때문

  • lazy stored property: 사용 불가

  • computed property: 값의 변화를 감지할 수 있으므로 사용 불가

willSetdidSet
시점값이 저장되기 직전에 호출됨값이 저장되고 난 직후에 호출됨
기본 파라미터 이름newValueoldValue
  • 형태
class Person {
    var phone: String {
        didSet { print("Subclass's didSet observer.") } // 세 번째로 실행
        willSet { print("Subclass's willSet observer.") } // 두 번째로 실행
    }
    
    init(phone: String) {
        self.phone = phone
    }
}

class Student: Person {
    var school: String
    var grade: Int = 1 {
        didSet { print("last year, grade was \(oldValue)") }
        willSet { print("this year, grade is \(newValue)") }
    }
    
    // 서브클래스의 프로퍼티 옵저버
    override var phone: String {
        didSet { print("from \(oldValue)") } // 마지막으로 실행
        willSet { print("Phone number changes to \(newValue)") } // 첫 번째로 실행
    }
    
    init(phone: String, school: String, grade: Int) {
        self.school = school
        self.grade = grade
        super.init(phone: phone)
    }
}

let student = Student(phone: "01012345678", school: "Harvard", grade: 2)
student.grade = 3
// this year, grade is 3
// last year, grade was 2

student.phone = "01098765432"
// Phone number changes to 01098765432
// Subclass's willSet observer.
// Subclass's didSet observer.
// from 01012345678

Type Properties

static 키워드를 사용해 타입 프로퍼티로 설정
인스턴스 프로퍼티와 다르게 해당 타입(class, struct, enum)에 존재하는 단 하나의 프로퍼티
특정 타입의 모든 인스턴스에서 공통으로 사용되는 값을 정의할 때 유용

타입 자체에는 생성자가 없기 때문에 항상 초기값을 지정해주어야 한다.

struct SomeStructure {
    static let storedTypeProperty = "SomeStructure's stored type property."
    static var someValue = 2
    static var computedTypeProperty: Int {
        return 3
    }
}

class SomeClass {
    static let storedTypeProperty = "SomeClass's stored type property."
    static var someValue = 2
    static var computedTypeProperty: Int {
        return 3
    }
}

enum SomeEnumeration {
    static let storedTypeProperty = "SomeStructure's stored type property."
    static var someValue = 2
    static var computedTypeProperty: Int {
        return 3
    }
}

print(SomeStructure.storedTypeProperty)
// SomeStructure's stored type property.

print(SomeClass.computedTypeProperty)
// 3

someEnumeration.someValue = 10
print(SomeEnumeration.someValue)
// 10

Property Wrapper

프로퍼티가 저장되는 방법을 관리하는 코드와
프로퍼티를 정의하는 코드 사이에 분리 계층을 추가

  • Wrapper을 정의할 때 스레드 안전성 검사, 데이터를 DB에 저장하는 기능을 작성
  • 해당 기능이 필요한 프로퍼티에 Wrapper을 적용하여 코드를 재사용
  • 정의할 때 내부에 wrappedValue 프로퍼티를 같이 정의
  • 컴파일러는 Wrapper을 위한 저장소를 제공하는 코드와
    Wrapper을 통해 프로퍼티에 접근하는 코드를 합성
@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height) // 0

rectangle.height = 10
print(rectangle.height) // 10

rectangle.height = 24
print(rectangle.height) // 12

Setting Initial Values for Wrapped Properties

Property Wrapper에 별다른 초기화 구문이 없다면, Wrapper을 사용하는 쪽은 Wrapping된 프로퍼티의 초기값을 그대로 따름
(위의 예제에서 SmallRectangleheight, width는 Wrapping된 프로퍼티인 number의 초기값 0을 가짐)

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

init()

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
// height, width = 0

초기값을 설정하지 않으면 Property Wrapper의 init()이 실행

init(wrappedValue:)

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
// height, width = 1

= 1init(wrappedValue:) 초기화 호출에 전달

  • SmallNumber(wrappedValue: 1) 호출로 인스턴스 생성

init(wrappedValue:maximum:)

struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    // or
    // @SmallNumber(maximum: 5) var height: Int = 2
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
    // or
    // @SmallNumber(maximum: 4) var height: Int = 3
}

var narrowRectangle = NarrowRectangle()
// height = 2, width = 3

narrowRectangle.height = 100
narrowRectangle.width = 100
// height = 5, width = 4

Property Wrapper 사용 시 커스텀 이니셜라이저를 사용 가능

Projecting a Value from a Property Wrapper

Wrapping된 값 외에도 투영된 값(Projected Value)에 의해 추가적인 기능을 노출 가능

  • 이름은 Wrapping된 값과 동일
  • 앞에 $ 표시를 붙여 사용
  • 어떤 타입의 값도 반환 가능
profile
초보 iOS 개발자입니다ㅏ

0개의 댓글