1. Stored Properties πŸ‘©β€πŸ’»

Class, Structure, Enumeration의 instance μΌλΆ€λ‘œμ¨ constant values λ˜λŠ” variable valuesλ₯Ό μ €μž₯ν•œλ‹€.

1. Stored Properties

FixedLengthRange instance λŠ” 1개의 variable firstValue 와 1개의 constant length λ₯Ό 가지고 μžˆλ‹€.

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


var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
print(rangeOfThreeItems)    // FixedLengthRange(firstValue: 0, length: 3)

rangeOfThreeItems.firstValue = 6
print(rangeOfThreeItems)    // FixedLengthRange(firstValue: 6, length: 3)

firstValue λŠ” var둜 μ„ μ–Έν–ˆκΈ° λ•Œλ¬Έμ— μˆ˜μ • κ°€λŠ₯ν•˜λ‹€.

rangeOfThreeItems.length = 4    // Cannot assign to property: 'length' is a 'let' constant

length λŠ” let으둜 μ„ μ–Έν–ˆκΈ° λ•Œλ¬Έμ— μˆ˜μ •μ΄ λΆˆκ°€λŠ₯ν•΄ μ—λŸ¬κ°€ λ°œμƒλœλ‹€.

2. Stored Properties of Constant Structure Instances

λ§Œμ•½ Structure 의 instance λ₯Ό 생성해 let ν‚€μ›Œλ“œμ— ν• λ‹Ήν•˜λ©΄, instance μžμ²΄κ°€ constant κ°€ λ˜λ―€λ‘œ propertiesκ°€ variable 이더라도 μˆ˜μ •μ΄ λΆˆκ°€λŠ₯ν•˜λ‹€.

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
rangeOfFourItems.firstValue = 3 // Cannot assign to property: 'rangeOfFourItems' is a 'let' constant


κ·ΈλŸ¬λ‚˜ 이것은 Structuresκ°€ Value Typesμ—¬μ„œ λ°œμƒν•˜λŠ” ν˜„μƒμœΌλ‘œ, Reference Types인 ClassesλŠ” instance λ₯Ό let ν‚€μ›Œλ“œλ₯Ό μ΄μš©ν•΄ constant 둜 선언해도, properties κ°€ variable 이면 μ—¬μ „νžˆ μˆ˜μ • κ°€λŠ₯ν•˜λ‹€.

class FixedVolumeRange {
    var firstValue: Int
    let volume: Int
    
    init(firstValue: Int, volume: Int) {
        self.firstValue = firstValue
        self.volume = volume
    }
}
let rangeOfFiveVolumes = FixedVolumeRange(firstValue: 0, volume: 5)
print("rangeOfFiveVolumes(firstValue: \(rangeOfFiveVolumes.firstValue), volume: \(rangeOfFiveVolumes.volume))")

rangeOfFiveVolumes.firstValue = 1
print("rangeOfFiveVolumes(firstValue: \(rangeOfFiveVolumes.firstValue), volume: \(rangeOfFiveVolumes.volume))")
rangeOfFiveVolumes(firstValue: 0, volume: 5)
rangeOfFiveVolumes(firstValue: 1, volume: 5)

3. Lazy Stored Properties

1 ) Syntax

Lazy Stored PropertiesλŠ” μ‚¬μš©λ˜κΈ° μ „κΉŒμ§€ μ΄ˆκΈ°κ°’μ΄ κ³„μ‚°λ˜μ§€ μ•ŠλŠ” Stored Property λ‹€. Property μ„ μ–Έ μ•žμ— lazy modifier λΆ™μ—¬ λ§Œλ“€λ©°, λ°˜λ“œμ‹œ var ν‚€μ›Œλ“œμ™€ ν•¨κ»˜ μ‚¬μš©ν•΄μ•Όν•œλ‹€. constant λŠ” initialization 이 μ’…λ£Œλ˜κΈ° 전에 λ°˜λ“œμ‹œ 값을 κ°€μ Έμ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€(= μ„ μ–Έκ³Ό λ™μ‹œμ— 값을 μ €μž₯ν•΄μ•Όν•œλ‹€).

Lazy Stored Properties λŠ” λ‹€μŒ 경우 μœ μš©ν•˜λ‹€

  • μ΄ˆκΈ°κ°’μ΄ μ™ΈλΆ€ μš”μΈμ— μ˜μ‘΄ν•˜λŠ” 경우
  • ν•„μš”ν•  λ•ŒκΉŒμ§€ μˆ˜ν–‰ν•˜λ©΄ μ•ˆ λ˜λŠ” 경우
  • μ΄ˆκΈ°κ°’μ„ μ €μž₯ν•˜λŠ”λ° λΉ„μš©μ΄ 많이 λ“œλŠ” 경우
  • μ΄ˆκΈ°κ°’μ΄ ν•„μš”ν•˜μ§€ μ•Šμ€ 경우


Syntax

struct SomeStructure {
    lazy var someProperty = {
        return // property definition goes here
    }()
    
    lazy var anotherProperty = SomeClass()  // or SomeStructure()
}


2 ) Lazy Stored Property Examples

  • μ΄ˆκΈ°κ°’μ΄ μ™ΈλΆ€ μš”μΈμ— μ˜μ‘΄ν•˜λŠ” 경우
class Classroom {
    let subject: Subject
    let maxStudents: Int
    var applicant: Int = 0

    lazy var students: Int = {
        applicant > maxStudents ? maxStudents : applicant
    }()

    enum Subject {
        case Korean, English, Math, History, Science
    }

    init(subject: Subject, maxStudents: Int) {
        self.subject = subject
        self.maxStudents = maxStudents
    }
}
struct Classroom {
    let subject: Subject
    let maxStudents: Int
    var applicant: Int = 0
    
    lazy var students: Int = {
        applicant > maxStudents ? maxStudents : applicant
    }()
    
    enum Subject {
        case Korean, English, Math, History, Science
    }
}

μœ„μ™€ 같이 lazyλŠ” Class 와 Structure λͺ¨λ‘μ—μ„œ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.

λ‹€μŒ μ˜ˆμ œλŠ” Structure λ₯Ό μ‚¬μš©ν–ˆλ‹€.

var mathClass = Classroom(subject: .Math, maxStudents: 30)
var englishClass = Classroom(subject: .English, maxStudents: 50)

μˆ˜ν•™ κ°•μ˜μ™€ μ˜μ–΄ κ°•μ˜λ₯Ό κ°œμ„€ν–ˆλ‹€. 그리고 μˆ˜ν•™ κ°•μ˜λŠ” μ΅œλŒ€ μˆ˜κ°• μ‹ μ²­ 인원을 30λͺ…, μ˜μ–΄ κ°•μ˜λŠ” 50λͺ…μœΌλ‘œ μ œν•œν–ˆλ‹€.
μ‹€μ œ κ°•μ˜λ₯Ό λ“£λŠ” 학생 수λ₯Ό 미리 μ•Œ 수 μ—†μ–΄ μˆ˜κ°• 인원은 lazy둜 계산을 보λ₯˜ν•˜λ„둝 ν–ˆλ‹€.

그리고 κ°•μ˜λ₯Ό μ˜€ν”ˆν•œ ν›„ 43λͺ…이 μˆ˜ν•™ κ°•μ˜λ₯Ό μ‹ μ²­ν–ˆλ‹€. κ°•μ˜ μˆ˜κ°• μ‹ μ²­ 기간이 μ’…λ£Œλ˜μ—ˆλ‹€. 이제 μˆ˜ν•™ κ°•μ˜λ₯Ό 좜λ ₯ν•΄λ³΄μž.

Array(1...43).forEach { i in mathClass.applicant += 1 }
print(mathClass)
Classroom(subject: __lldb_expr_48.Classroom.Subject.Math, 
          maxStudents: 30, 
          applicant: 43, 
          $__lazy_storage_$_students: nil)

lazy둜 인해 아직 κ°•μ˜λ₯Ό λ“£λŠ” 학생 μˆ˜λŠ” 정해지지 μ•Šμ•„ nil인 것을 확인할 수 μžˆλ‹€.
μ΄λ²ˆμ—λŠ” Lazy Stored Property λ₯Ό μ‚¬μš©ν•΄ μ΄ˆκΈ°κ°’μ΄ μ €μž₯λ˜λ„λ‘ ν•΄λ³΄μž.

mathClass.students
print(mathClass)
Classroom(subject: __lldb_expr_48.Classroom.Subject.Math, 
          maxStudents: 30, 
          applicant: 43, 
          $__lazy_storage_$_students: Optional(30))

Classroom Structure 의 students property λŠ” μ΄ˆκΈ°κ°’μ΄ μ—†μœΌλ―€λ‘œ nil을 ν—ˆμš©ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— Int둜 μ„ μ–Έν–ˆμŒμ—λ„ λΆˆκ΅¬ν•˜κ³  instance 자체λ₯Ό 좜λ ₯ν•˜λ©΄ Int? 즉, Optional 인 것을 확인할 수 μžˆλ‹€.
λ˜ν•œ μ΄λ•Œμ˜ property λŠ” students κ°€ μ•„λ‹Œ $__lazy_storage_$_students인 것도 확인할 수 μžˆλ‹€.


print("\(mathClass.students) students in math class")   // 30 students in math class

ν•˜μ§€λ§Œ ν•΄λ‹Ή Property λ₯Ό 직접 접근해보면 μš°λ¦¬κ°€ μ •μ˜ν•œ Int νƒ€μž…μ˜ 값을 μ–»μ–΄μ˜€λŠ” 것을 λ³Ό 수 μžˆλ‹€. μ΄λŠ” Lazy Stored Properties λ₯Ό μ‚¬μš©ν•˜λŠ” μˆœκ°„ Closure κ°€ μ‹€ν–‰λ˜λ©° 값을 μ €μž₯ν–ˆκΈ° λ•Œλ¬Έμ΄λ‹€. 즉, lazy둜 인해 값을 ν• λ‹Ή(μ €μž₯)ν•˜λŠ” 것이 지연이 λœλ‹€λŠ” 것을 μ œμ™Έν•˜λ©΄ Lazy Stored Properties λŠ” 일반적인 Stored Properties 와 κ°™λ‹€λŠ” 것을 μ•Œ 수 μžˆλ‹€.


μ΄λ²ˆμ—λŠ” μ˜μ–΄ κ°•μ˜λ₯Ό μ˜€ν”ˆν–ˆκ³ , 45λͺ…이 μ˜μ–΄ κ°•μ˜λ₯Ό μ‹ μ²­ν–ˆλ‹€.

Array(1...10).forEach { i in englishClass.applicant += 1 }
print("\(englishClass.students) students in english class")    // 10 students in english class

Array(1...35).forEach { i in englishClass.applicant += 1 }
print("\(englishClass.students) students in english class")    // 10 students in english class

print(englishClass) // Classroom(subject: __lldb_expr_74.Classroom.Subject.English, maxStudents: 50, applicant: 45, $__lazy_storage_$_students: Optional(10))

그런데 10λͺ…이 μ‹ μ²­ν•œ μ‹œμ μ— 학생 수λ₯Ό ν•œ 번 μ‘°νšŒν–ˆλ‹€. 총 45λͺ…이 지원을 ν–ˆμ§€λ§Œ μ—¬μ „νžˆ 학생 μˆ˜λŠ” 10λͺ…μœΌλ‘œ 좜λ ₯λœλ‹€!!
μˆ˜κ°• μ‹ μ²­ 기간이 μ’…λ£Œλœ ν›„ μ‘°νšŒν–ˆμ„ λ•Œ μ§€μ›μž μˆ˜λŠ” 45λͺ…μœΌλ‘œ μ •μƒμ μœΌλ‘œ μ €μž₯ λ˜μ—ˆμœΌλ‚˜ ν•™μƒμˆ˜λ§Œ 10λͺ…인 μƒνƒœμΈ 것을 λ³Ό 수 μžˆλ‹€.
μ΄λŠ” 이미 10λͺ…이 μ‹ μ²­ν•œ μ‹œμ μ— ν•΄λ‹Ή Lazy Stored Property 에 μ ‘κ·Όν•΄ μ΄ˆκΈ°κ°’μ΄ μ €μž₯λ˜μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

Lazy Stored PropertiesλŠ” 졜초 μ‚¬μš©λ˜λŠ” μˆœκ°„μ— 값을 μ €μž₯ν•œλ‹€.
이후 λ‹€μ‹œ μ‚¬μš©ν•  λ•ŒλŠ” 이미 지연 μ €μž₯된 값을 κ°€μ Έμ˜€λ―€λ‘œ 값이 μ—…λ°μ΄νŠΈ λ˜μ§€ μ•ŠλŠ”λ‹€.
λ§Œμ•½, 값을 맀번 μ—…λ°μ΄νŠΈ ν•˜κΈ°λ₯Ό μ›ν•œλ‹€λ©΄ μ €μž₯ν•˜λŠ” 것이 μ•„λ‹ˆλΌ κ³„μ‚°ν•˜λ„λ‘ Computed Propertiesλ₯Ό μ‚¬μš©ν•΄μ•Όν•œλ‹€.


  • μ΄ˆκΈ°κ°’μ„ μ €μž₯ν•˜λŠ”λ° λΉ„μš©μ΄ 많이 λ“œλŠ” 경우
class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a nontrivial amount of time to initialize.
    */
    var filename = "data.txt"
    // the DataImporter class would provide data importing functionality here
}

class DataManager {
    lazy var importer = DataImporter()
    var data: [String] = []
    // the DataManager class would provide data management functionality here
}
  • DataImporter ν΄λž˜μŠ€λŠ” μ™ΈλΆ€ νŒŒμΌλ‘œλΆ€ν„° 데이터λ₯Ό κ°€μ Έμ˜¨λ‹€.
  • DataManager ν΄λž˜μŠ€λŠ” data λΌλŠ” μ΄λ¦„μ˜ Stored Property λ₯Ό λ‹€λ£¨λŠ” 클래슀둜 [String]으둜 μ €μž₯된 데이터λ₯Ό 닀룬닀. 그리고 이 ν΄λž˜μŠ€λŠ” νŒŒμΌλ‘œλΆ€ν„° 데이터λ₯Ό κ°€μ Έμ˜¬ 수 μžˆλ„λ‘ DataImporter 클래슀λ₯Ό ν¬ν•¨ν•˜κ³ μžˆλ‹€.

ν•˜μ§€λ§Œ μ™ΈλΆ€ νŒŒμΌμ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” 것은 항상 ν•„μš”ν•œ 것이 μ•„λ‹ˆλ‹€. 그리고 μ΄λŸ¬ν•œ κΈ°λŠ₯을 μ΄ˆκΈ°ν™”ν•˜λŠ” 것은 λΉ„μš©μ΄ 많이 λ“œλŠ” μž‘μ—…μ΄λ‹€. λ”°λΌμ„œ ν•΄λ‹Ή instance λŠ” 처음 μ‚¬μš©ν•  λ•Œ μƒμ„±ν•˜λŠ” 것이 합리적이닀. κ·ΈλŸ¬λ―€λ‘œ DataManager λŠ” 이것을 Lazy Stored Property 둜 μ„ μ–Έν–ˆλ‹€.


let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")

DataManager 의 instance 에 λ¬Έμžμ—΄ 2개λ₯Ό μ €μž₯ν–ˆλ‹€. ν•˜μ§€λ§Œ 아직 μ™ΈλΆ€ νŒŒμΌμ„ κ°€μ Έμ˜¬ 일이 μ—†μ—ˆκΈ° λ•Œλ¬Έμ— DataImporter instance λŠ” μƒμ„±λ˜μ§€ μ•Šμ•˜λ‹€.


print(manager.importer.filename)
// Prints "data.txt"

DataManager 클래슀의 importer property 에 λŒ€ν•œ DataImportor instance κ°€ μƒμ„±λ˜μ—ˆλ‹€!!

Lazy Stored Propertiesλ₯Ό λ©€ν‹° μŠ€λ ˆλ“œμ—μ„œ λ™μ‹œμ— access ν•  λ•Œ 아직 properties κ°€ μ΄ˆκΈ°ν™” λ˜μ§€ μ•Šμ•˜λ‹€λ©΄, ν•œ 번만 μ΄ˆκΈ°ν™”λœλ‹€λŠ” 보μž₯이 μ—†λ‹€. 즉, Thread-UnSafeν•˜λ―€λ‘œ 이λ₯Ό μ œμ–΄ν•  ν•„μš”κ°€ μžˆλ‹€.

4. Stored Properties and Instance Variables

Objective-C λŠ” Class instance 의 Properties 둜 Values 와 References λ₯Ό μ €μž₯ν•˜λŠ” 두 가지 방법을 μ œκ³΅ν–ˆλ‹€. λ˜ν•œ Properties λ₯Ό Backing Store(λ°±μ—… μ €μž₯μ†Œ)둜 μ‚¬μš©ν•  수 μžˆμ—ˆλ‹€.

ν•˜μ§€λ§Œ Swift λŠ” Backing Store에 직접 접속할 수 없도둝 ν•˜κ³ , Propertiesλ₯Ό μ €μž₯ν•˜λŠ” 방식을 ν†΅ν•©ν–ˆλ‹€. λ”°λΌμ„œ μ„ μ–Έν•˜λŠ” 방법에 λ”°λ₯Έ ν˜Όλ™μ„ ν”Όν•˜κ³  λͺ…ν™•ν•œ λ¬Έμž₯으둜 λ‹¨μˆœν™”λ˜μ—ˆμœΌλ©°, μ΄λŠ” Properties의 이름, νƒ€μž…, λ©”λͺ¨λ¦¬ 관리 νŠΉμ„±μ„ ν¬ν•¨ν•˜λŠ” λͺ¨λ“  정보λ₯Ό μœ ν˜•μ„ ν•œ κ³³μ—μ„œ μ •μ˜ν•œλ‹€.


2. Computed Properties πŸ‘©β€πŸ’»

1. Computed Properties

1 ) Syntax

Class, Structure, Enumeration 의 μΌλΆ€λ‘œμ¨ 값을 μ €μž₯ν•˜λŠ” λŒ€μ‹  κ³„μ‚°ν•˜λ©°, getter와 optional setterλ₯Ό μ œκ³΅ν•œλ‹€. Lazy Stored Properties 와 λ§ˆμ°¬κ°€μ§€λ‘œ λ°˜λ“œμ‹œ var ν‚€μ›Œλ“œμ™€ ν•¨κ»˜ μ‚¬μš©ν•΄μ•Όν•˜λ©°, Lazy Stored Properties 와 λ‹€λ₯΄κ²Œ λ°˜λ“œμ‹œ 데이터 νƒ€μž…μ„ λͺ…μ‹œ(explicit type)ν•΄μ•Όν•œλ‹€.

λ˜ν•œ, 값을 ν• λ‹Ή(μ €μž₯)ν•˜λŠ” 것이 μ•„λ‹ˆλ―€λ‘œ, =λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , explicit type λ‹€μŒ λ°”λ‘œ getter 와 setter λ₯Ό κ°–λŠ” Closureλ₯Ό μž‘μ„±ν•œλ‹€. λ˜ν•œ setter 의 parameter λŠ” λ°˜λ“œμ‹œ λͺ…μ‹œλœ explicit type κ³Ό λ™μΌν•œ SomeType μ΄μ–΄μ•Όν•˜λ―€λ‘œ, λ³„λ„μ˜ type을 λͺ…μ‹œν•  수 μ—†λ‹€.

Syntax

struct SomeStructure {
    var someProperty: SomeType {
        get {
            return // property definition for getter goes here
        }
        set (parameterName) {
            // property definition for setter goes here
        }
    }
}

단!! Computed PropertiesλŠ” μ ˆλŒ€ 자기 μžμ‹ μ„ λŒ€μƒμœΌλ‘œ ν•΄μ„œλŠ” μ•ˆ λœλ‹€.
κ°•ν•œ μ°Έμ‘°κ°€ μƒμ„±λ˜κΈ° λ•Œλ¬Έμ΄λ‹€.

struct SomeStructure {
    var someProperty: SomeType {
        get {
            self.someProperty
        }
        set {
            self.someProperty = newValue
        }
    }
}

Infinite Recursion


2 ) Computed Property Examples

  • Case 1

첫 번째 예재둜 μœ„ Lazy Stored Properties μ—μ„œ μ§λ©΄ν–ˆλ˜ μ˜μ–΄ κ°•μ˜ 학생 수λ₯Ό 확인할 λ•Œ κ²ͺμ—ˆλ˜ 문제λ₯Ό ν•΄κ²°ν•΄λ³΄μž.

struct Classroom {
    let subject: Subject
    let maxStudents: Int
    var applicant: Int = 0
    
    var students: Int {
        get {
            applicant > maxStudents ? maxStudents : applicant
        }
    }
    
    enum Subject {
        case Korean, English, Math, History, Science
    }
}

var englishClass = Classroom(subject: .English, maxStudents: 50)


μœ„μ™€ λ™μΌν•˜κ²Œ μ˜μ–΄ κ°•μ˜λ₯Ό μ˜€ν”ˆν•˜κ³  μˆ˜κ°• 신청이 μ§„ν–‰λ˜λŠ” 도쀑 μ—¬λŸ¬ μ°¨λ‘€ 학생 수λ₯Ό ν™•μΈν–ˆλ‹€.

Array(1...10).forEach { i in englishClass.applicant += 1 }
print("\(englishClass.students) students in math class")    // 10 students in math class

Array(1...35).forEach { i in englishClass.applicant += 1 }
print("\(englishClass.students) students in math class")    // 45 students in math class

Array(1...10).forEach { i in englishClass.applicant += 1 }
print("\(englishClass.students) students in math class")    // 50 students in math class

μœ„ Lazy Stored Properties μ—μ„œ κ²ͺμ—ˆλ˜ λ¬Έμ œμ™€ 달리 맀번 μ΅œμ‹  값을 얻을 수 μžˆλ‹€! 이것은 Computed Properties κ°€ μ‹€μ œλ‘œ 값을 μ €μž₯ν•˜λŠ” 것이 μ•„λ‹Œ 계산 ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

print(englishClass)
Classroom(subject: main.Classroom.Subject.English, 
          maxStudents: 50,
          applicant: 55)

Lazy Stored Properties 와 λ‹€λ₯΄κ²Œ instance λ₯Ό μ‘°νšŒν•  λ•Œ μ‘°νšŒκ°€ λ˜μ§€ μ•ŠλŠ”λ‹€. μ €μž₯λ˜λŠ” 값이 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€.
즉, Propertiesμ§€λ§Œ 행동은 Methods에 가깝닀(κ·Έλ ‡λ‹€κ³  이것이 Methods 인 것은 μ•„λ‹ˆλ‹€. μ—¬μ „νžˆ Properties λ‹€).


  • Case 2

μ΄λ²ˆμ—λŠ” 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)
        }
    }
}
  • Point: Cartesian Coordinates System μœ„μ— μžˆλŠ” 점의 μœ„μΉ˜ λ₯Ό encapsulates(μΊ‘μŠν™”) ν•œλ‹€.
  • Size: μ‚¬κ°ν˜•μ˜ λ„ˆλΉ„ 와 폭 을 encapsulates(μΊ‘μŠν™”) ν•œλ‹€.
  • Rect: μ‚¬κ°ν˜•μ„ μ •μ˜ν•œλ‹€. 이λ₯Ό μœ„ν•΄ Point와 Size의 instances λ₯Ό 각각 origin κ³Ό size λΌλŠ” Stored Properties 둜 κ°–κ³ , μ •μ˜λœ μ‚¬κ°ν˜•μ˜ 쀑심점을 κ΅¬ν•˜κΈ° μœ„ν•œ getter 와, 쀑심점이 μ΄λ™λ˜μ—ˆμ„ λ•Œ μƒˆ 쀑심점에 따라 기쀀점 origin 을 μž¬μ •μ˜ν•˜λŠ” setter λ₯Ό κ°–λŠ” Computed Propertyλ₯Ό center λΌλŠ” μ΄λ¦„μœΌλ‘œ κ°–κ³  μžˆλ‹€.


var square = Rect(origin: Point(),
                  size: Size(width: 10, height: 10))

print(square.center)    // Point(x: 5.0, y: 5.0)

square instance λ₯Ό λ§Œλ“€μ—ˆκ³ , μƒμ„±λœ instance λ‘œλΆ€ν„° getter λ₯Ό μ΄μš©ν•΄ μ‚¬κ°ν˜•μ˜ 쀑심점을 κ΅¬ν–ˆλ‹€.
μ΄λ²ˆμ—λŠ” setter λ₯Ό μ΄μš©ν•΄ μƒˆ 기쀀점을 μ €μž₯ν•˜κ³ , λ³€κ²½λœ 기쀀점과 κ·Έλ•Œμ˜ 쀑심점을 κ΅¬ν•΄λ³΄μž.

square.center = Point(x: 17.5, y: 17.5)
print("""
square.origin: \(square.origin)
square.center: \(square.center)
""")
square.origin: Point(x: 12.5, y: 12.5)
square.center: Point(x: 17.5, y: 17.5)

2. Shorthand Getter/Setter Declaration

  • Shorthand Setter Declaration

Trailing Closures κ°€ Parameters λ₯Ό μƒλž΅ν•˜λ©΄ κΈ°λ³Έκ°’μœΌλ‘œ $0, $1, $2, ...λ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμ²˜λŸΌ Computed Properties 의 setter 의 Parameters λ₯Ό μƒλž΅ν•˜λ©΄ κΈ°λ³Έκ°’μœΌλ‘œ newValue와 oldValueλ₯Ό μ‚¬μš©ν•œλ‹€.

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 {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}


  • Shorthand Getter Declaration

λ‹€λ₯Έ Closures 와 λ§ˆμ°¬κ°€μ§€λ‘œ single expression 으둜 μž‘μ„±λ˜λ©΄ return ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•  수 μžˆλ‹€.

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

3. Read-Only Computed Properties

μœ„ 2.1의 Case 1 μ˜μ–΄ κ°•μ˜ 예제λ₯Ό λ‹€μ‹œ 보자. setter κ°€ ν•„μš” μ—†κ³  getter 만 ν•„μš”ν•œ 경우 이λ₯Ό Read-Only Computed Properties라고 ν•˜λ©°, get ν‚€μ›Œλ“œμ™€ μ€‘κ΄„ν˜Έ{ }λ₯Ό μƒλž΅ν•  수 μžˆλ‹€.

struct Classroom {
    let subject: Subject
    let maxStudents: Int
    var applicant: Int = 0
    
    var students: Int {
        applicant > maxStudents ? maxStudents : applicant
    }
    
    enum Subject {
        case Korean, English, Math, History, Science
    }
}

3. Property Observers πŸ‘©β€πŸ’»

1. Definition of Property Observers

Property ObserversλŠ” Property 의 값에 set의 λ³€ν™”λ₯Ό κ΄€μ°°ν•˜κ³  μ‹€ν–‰λœλ‹€. μƒˆ 값이 기쑴의 κ°’κ³Ό 같더라도 set 이 λ°œμƒν•˜λŠ” 것 자체둜 trigger 되기 λ•Œλ¬Έμ— ν˜ΈμΆœλœλ‹€.


1 ) Attach Observers

Property 에 Observersλ₯Ό 뢙일 수 μžˆλŠ” 곳은 λ‹€μŒκ³Ό κ°™λ‹€.

  • Stored Properties
  • μƒμ†λœ Stored Properties
  • μƒμ†λœ Computed Properties

μƒμ†λœ Properties 에 Property Observers λ₯Ό 뢙일 λ•ŒλŠ” overriding 을 μ΄μš©ν•œλ‹€.

μƒμ†λ˜μ§€ μ•Šμ€ Computed Properties λŠ” Property Observers λ₯Ό μ‚¬μš©ν•  수 μ—†μœΌλ―€λ‘œ, λŒ€μ•ˆμœΌλ‘œ Computed Properties 의 setter λ₯Ό μ‚¬μš©ν•΄ 일정 λΆ€λΆ„ μœ μ‚¬ν•˜κ²Œ κ΅¬ν˜„ν•˜λŠ” 방법이 μžˆλ‹€.


2 ) willSet & didSet

Computed Properties λŠ” setter와 getterλΌλŠ” 2가지 μ˜΅μ…˜μ΄ μ‘΄μž¬ν–ˆλ‹€.
Property Observers λŠ” willSetκ³Ό didSetμ΄λΌλŠ” 2가지 μ˜΅μ…˜μ΄ μ‘΄μž¬ν•œλ‹€.

  • willSet : 값이 μ €μž₯되기 직전에 호좜되며, Parameters λ₯Ό μƒλž΅ν•˜λ©΄ κΈ°λ³Έκ°’μœΌλ‘œ newValueλ₯Ό μ‚¬μš©ν•œλ‹€.
  • didSet : 값이 μ €μž₯된 직후에 호좜되며, Parameters λ₯Ό μƒλž΅ν•˜λ©΄ κΈ°λ³Έκ°’μœΌλ‘œ oldValueλ₯Ό μ‚¬μš©ν•œλ‹€.


Syntax

class SomeClass {
    var someProperty: Type = defaultValue {
        willSet {
            // observer definition for willSet goes here
        }
        didSet {
            // observer definition for didSet goes here
        }
    }
}

Lazy Stored Properties λ˜λŠ” Computed Properties 와 λ§ˆμ°¬κ°€μ§€λ‘œ λ°˜λ“œμ‹œ var ν‚€μ›Œλ“œμ™€ ν•¨κ»˜ μ‚¬μš©ν•œλ‹€. λ˜ν•œ μ΄ˆκΈ°κ°’μ„ λ°˜λ“œμ‹œ μ •μ˜ν•΄μ•Όν•˜λ©°, λ‘œμ§μ€ Trailing Closures λ₯Ό μ΄μš©ν•΄ μ •μ˜ν•œλ‹€.

Lazy Stored Properties 와 Computed Properties, Property Observers λŠ” Syntax κ°€ μœ μ‚¬ν•΄ ν—·κ°ˆλ¦¬κΈ° 쉽닀. 닀같이 놓고 비ꡐ해보면 λ‹€μŒκ³Ό 같은 차이λ₯Ό 보인닀.

  • Lazy Stored Properties
lazy var someProperty = {
    return // property definition goes here
}()

lazy var anotherProperty = SomeClass()  // or SomeStructure()

Lazy Stored Properties λŠ” Closures λ˜λŠ” Classes 의 Initializers 에 ()λ₯Ό λΆ™μ—¬ μ‹€ν–‰ν•˜κ³  λ°˜ν™˜λœ 값을 λ°”λ‘œ λ³€μˆ˜μ— μ €μž₯ν•˜λ„λ‘ λ˜μ–΄μžˆλ‹€. λŒ€μ‹  μ €μž₯을 μ§€μ—°μ‹œν‚€κΈ° μœ„ν•΄ lazy modifier λ₯Ό μž‘μ„±ν–ˆλ‹€.

  • Computed Properties
var someProperty: SomeType {
    get {
        return // property definition for getter goes here
    }
    set (parameterName) {
        // property definition for setter goes here
    }
}

Computed Properties λŠ” κΈ°λ³Έκ°’ μ €μž₯ 없이 get, set을 λ©”μ„œλ“œλ‘œ κ°–λŠ” Trailing Closures λ₯Ό μž‘μ„±ν•΄ μ •μ˜ν•œλ‹€. Lazy Stored Properties 와 달리 () λ₯Ό μž‘μ„±ν•˜μ§€ μ•ŠλŠ”λ‹€.

  • Property Observers
var someProperty: Type = defaultValue {
    willSet {
        // observer definition for willSet goes here
    }
    didSet {
        // observer definition for didSet goes here
    }
}

기본값을 μ €μž₯ν•˜κ³  뒀에 willSet, didSet을 λ©”μ„œλ“œλ‘œ κ°–λŠ” Trailing Closures λ₯Ό μž‘μ„±ν•΄ μ •μ˜ν•œλ‹€.


3 ) Initializer of subclass

Property Observers 의 willSet, didSet 은 Initializers 에 μ˜ν•΄ Instance κ°€ 생성될 λ•ŒλŠ” μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. Initializers 에 μ˜ν•΄ Instance κ°€ μƒμ„±λ˜κ³  λ‚œ 이후에 Observers κ°€ μž‘λ™ν•œλ‹€.

λ”°λΌμ„œ λ‹€μŒκ³Ό 같은 과정을 거치게 λœλ‹€.

  1. Subclass κ°€ μžμ‹ μ˜ Properties 의 속성을 λͺ¨λ‘ μ„€μ •ν•œ ν›„ Superclass 의 Initializers λ₯Ό ν˜ΈμΆœν•œλ‹€.
  2. Superclass κ°€ μžμ‹ μ˜ Designated Initializers λ₯Ό μ΄μš©ν•΄ Initialization 을 μˆ˜ν–‰ν•œλ‹€. μ΄λ•Œ Superclass μžμ‹ μ΄ κ°–κ³  μžˆλŠ” Observers λŠ” μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. 이둜써 Phase 1 이 μ’…λ£Œλœλ‹€.
  3. 이제 Phase 2κ°€ μ§„ν–‰λ˜κ³  Subclass 의 Initializers κ°€ Superclass 의 Properties λ₯Ό μˆ˜μ •ν•œλ‹€. μ΄λ•Œ ν•΄λ‹Ή Properties 에 Observers κ°€ λΆ™μ–΄μžˆλ‹€λ©΄ willSet, didSet이 μž‘λ™ν•œλ‹€.

2. Property Observer Examples

μ•„λž˜ 걸음수 데이터λ₯Ό μ €μž₯ν•˜λŠ” StepCounter κ°€ μžˆλ‹€.

class StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            if newValue > totalSteps {
                print("About to set totalSteps to \(newValue)")
            } else {
                print("Please check your step data")
                return
            }
            
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps, totalStep is now \(totalSteps)")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
About to set totalSteps to 200
Added 200 steps, totalStep is now 200

200보λ₯Ό μ €μž₯ν–ˆλ‹€. μ΄ˆκΈ°κ°’μ€ 0μ΄λ―€λ‘œ (200 - 0)을 계산해 β€˜200보가 μΆ”κ°€λ˜μ—ˆκ³ , ν˜„μž¬ 총 κ±ΈμŒμˆ˜λŠ” 200λ³΄β€™μž„μ„ 좜λ ₯ν•œλ‹€.


stepCounter.totalSteps = 100
Please check your step data

μ•žμ—μ„œ μ €μž₯ν•œ 전체 κ±ΈμŒμˆ˜κ°€ 200λ³΄μ˜€λŠ”λ° 전체 걸음수λ₯Ό 100보 μ €μž₯ν•˜λ €κ³  ν•œλ‹€. willSet 이 이λ₯Ό κ±°μ ˆν•˜κ³  λ©”μ‹œμ§€λ₯Ό λ‚¨κ²ΌμœΌλ©°, didSet 은 μΌμΉ˜ν•˜λŠ” 쑰건이 μ—†μ–΄ μ’…λ£Œλ˜μ—ˆλ‹€.


stepCounter.totalSteps = 360
About to set totalSteps to 360
Added 260 steps, totalStep is now 360

λ‹€μ‹œ 360보λ₯Ό μ €μž₯ν•˜λ‹ˆ μ •μƒμ μœΌλ‘œ μ €μž₯이 λ˜μ—ˆλ‹€. ν•˜μ§€λ§Œ 이전에 μ €μž₯ν•œ 200보λ₯Ό κΈ°μ€€μœΌλ‘œ (360-200)을 ν•΄μ„œ β€˜160보가 μΆ”κ°€λ˜μ—ˆκ³ , ν˜„μž¬ 총 κ±ΈμŒμˆ˜λŠ” 360λ³΄β€™μž„μ„ 좜λ ₯ν• κ²ƒμœΌλ‘œ μ˜ˆμƒν–ˆμœΌλ‚˜ 거절 λ©”μ‹œμ§€λ₯Ό λ‚¨κ²Όλ˜ 100보가 μ‹€μ œλ‘œλŠ” μ €μž₯λ˜μ–΄ β€˜260보가 μΆ”κ°€λ˜μ—ˆκ³ , ν˜„μž¬ 총 κ±ΈμŒμˆ˜λŠ” 360보’라고 좜λ ₯ν•œλ‹€.

μœ„μ—μ„œ willSet 이 거절 λ©”μ‹œμ§€λ₯Ό 남기며 return을 ν–ˆμ§€λ§Œ μ‹€μ œλ‘œ κ°’μ˜ μ €μž₯을 λ§‰μ§€λŠ” λͺ»ν–ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

willSet은 값을 μ €μž₯ν•˜κΈ° μ§μ „μ˜ 행동을 μ •μ˜ν•  수 μžˆμ„ 뿐 값을 μ €μž₯ν•˜λŠ” ν–‰μœ„ 자체λ₯Ό μ œμ–΄ν•˜μ§€λŠ” λͺ»ν•œλ‹€!!


μœ„ Classλ₯Ό 고쳐 Validation Checkκ°€ κ°€λŠ₯ν•˜λ„λ‘ ν•΄λ³΄μž.

class StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            if newValue > totalSteps {
                print("About to set totalSteps to \(newValue)")
            }
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps, totalStep is now \(totalSteps)")
            } else {
                print("Please check your step data")
                totalSteps = oldValue
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
print("--------------------------------------")
stepCounter.totalSteps = 100
print("--------------------------------------")
stepCounter.totalSteps = 360

About to set totalSteps to 200
Added 200 steps, totalStep is now 200
--------------------------------------
Please check your step data
--------------------------------------
About to set totalSteps to 360
Added 160 steps, totalStep is now 360

μ΄λ²ˆμ—λŠ” 360보λ₯Ό μ €μž₯ν•  λ•Œ 기쑴의 200λ³΄μ—μ„œ 160보가 μΆ”κ°€λ˜μ—ˆλ‹€.

Validation Checkκ°€ ν•„μš”ν•˜λ‹€λ©΄ Property ObserversλŠ” μ ν•©ν•˜μ§€ μ•Šλ‹€. μœ„ μ˜ˆμ œμ—μ„œ λ³Ό 수 μžˆλ“―μ΄ 값을 μ €μž₯ν•˜λŠ” ν–‰μœ„ 자체λ₯Ό μ œμ–΄ν•˜μ§€λŠ” λͺ» ν•˜κ³  μ €μž₯ ν›„ λ‹€μ‹œ κΈ°μ‘΄ κ°’μœΌλ‘œ λ‘€λ°±ν•˜λŠ” 것이기 λ•Œλ¬Έμ΄λ‹€.

λ˜ν•œ Computed Propertiesλ₯Ό μ΄μš©ν•˜λŠ” 것 μ—­μ‹œ 자기 μžμ‹ μ—κ²Œ μ μš©ν•˜λ©΄ κ°•ν•œ μˆœν™˜ μ°Έμ‘°λ₯Ό μƒμ„±ν•˜λ―€λ‘œ μ ν•©ν•˜μ§€ μ•Šλ‹€. λ”°λΌμ„œ Validation Check κ°€ ν•„μš”ν•  경우 λ‹¨μˆœν•˜κ³  곡톡화가 κ°€λŠ₯ν•˜λ‹€λ©΄ Property Wrappersλ₯Ό μ‚¬μš©ν•˜κ³ , 그렇지 μ•Šμ„ 경우 setter λ©”μ„œλ“œλ₯Ό λ³„λ„λ‘œ μ •μ˜ν•˜λŠ” 것이 μ’‹λ‹€.


λ§ˆμ§€λ§‰μœΌλ‘œ Property Observers λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” λ‹€μŒ 경우λ₯Ό μ‘°μ‹¬ν•΄μ•Όν•œλ‹€.

Observersκ°€ 뢙은 Properties λ₯Ό ν•¨μˆ˜μ˜ In-Out Parameters둜 μ „λ‹¬ν•˜λ©΄, willSetκ³Ό didSet은 항상 ν˜ΈμΆœλœλ‹€. μ΄λŠ” In-Out Parametersκ°€ Copy-in Copy-out Memory Model에 μ˜ν•΄ ν•¨μˆ˜κ°€ μ’…λ£Œλ  λ•Œ 항상 값을 λ‹€μ‹œ μ €μž₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.


4. Property Wrappers πŸ‘©β€πŸ’»

1. Property Wrappers

1 ) Syntax

Property WrappersλŠ” Properties λ₯Ό μ •μ˜ν•˜λŠ” μ½”λ“œμ™€ μ €μž₯λ˜λŠ” 방법을 κ΄€λ¦¬ν•˜λŠ” μ½”λ“œ 사이에 λΆ„λ¦¬λœ layer(계측)을 μΆ”κ°€ν•œλ‹€.

예λ₯Ό λ“€μ–΄ Thread-Safe 검사λ₯Ό μ œκ³΅ν•˜λŠ” Properties, λ˜λŠ” κΈ°λ³Έ 데이터λ₯Ό Database 에 μ €μž₯ν•˜λŠ” Properties κ°€ μžˆλŠ” 경우 ν•΄λ‹Ή μ½”λ“œλ₯Ό λͺ¨λ“  Properties 에 μž‘μ„±ν•΄μ•Όν•œλ‹€. μ΄λ•Œ Property Wrappersλ₯Ό μ‚¬μš©ν•˜λ©΄ μ½”λ“œλ₯Ό ν•œ 번만 μž‘μ„±ν•˜κ³  μž¬μ‚¬μš© ν•  수 μžˆλ‹€.


Syntax

@propertyWrapper
struct SomeStructure {
    private var someProperty: SomeType
    var wrappedValue: SomeType {
        get { someProperty }
        set { someProperty = newValue }
    }
}
  • Class, Structure, Enumerationλ₯Ό μ΄μš©ν•΄ μ •μ˜ν•˜λ©° 3가지 λΆ€λΆ„μœΌλ‘œ λ‚˜λ‰œλ‹€

  • @propertyWrapper Annotation 을 μ„ μ–Έ
  • private var λ³€μˆ˜ μ„ μ–Έ
  • wrappedValue λΌλŠ” 이름을 κ°–λŠ” Computed Propertyλ₯Ό μ •μ˜


  • Without @propertyWrapper Annotation

1 ~ 9 κΉŒμ§€μ˜ 두 수λ₯Ό λ°›μ•„ ꡬꡬ단을 κ³„μ‚°ν•΄λ³΄μž. 1보닀 μž‘μ€ μˆ˜λŠ” 1둜, 9보닀 큰 μˆ˜λŠ” 9둜 λ³€κ²½ν•˜λ„λ‘ ν•œλ‹€.
기쑴의 λ°©μ‹λŒ€λ‘œ @propertyWrapper 없이 explicit wrapping을 ν•˜λŠ” 방법뢀터 μ•Œμ•„λ³΄μž.

struct OneToNine {
    private var number = 1
    var wrappedValue: Int {
        get { number }
        set { number = max(min(newValue, 9), 1) }
    }
}
// Explicit Wrapping
struct MultiplicationTable {
    private var _left = OneToNine()
    private var _right = OneToNine()
    var left: Int {
        get { _left.wrappedValue }
        set { _left.wrappedValue = newValue }
    }
    var right: Int {
        get { _right.wrappedValue }
        set { _right.wrappedValue = newValue }
    }
}


  • With @propertyWrapper Annotation

@propertyWrapper 없이 wrapping을 ν•˜λ©΄ λͺ¨λ“  λ³€μˆ˜λ§ˆλ‹€ λͺ…μ‹œμ μœΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Όν•œλ‹€. 즉, μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ ΅λ‹€λŠ” λœ»μ΄λ‹€.
μš°λ¦¬λŠ” 이 문제λ₯Ό @propertyWrapperλ₯Ό 톡해 μ•„λž˜μ™€ 같이 ν•΄κ²°ν•  수 μžˆλ‹€.

@propertyWrapper
struct OneToNine {
    private var number = 1
    var wrappedValue: Int {
        get { number }
        set { number = max(min(newValue, 9), 1) }
    }
}
struct MultiplicationTable {
    @OneToNine var left: Int
    @OneToNine var right: Int
}


var multiplication = MultiplicationTable()

multiplication.left = 7
multiplication.right = 8
print("\(multiplication.left) x \(multiplication.right) = \(multiplication.left * multiplication.right)")
// Prints "7 x 8 = 56"

multiplication.left = 10
multiplication.right = 5
print("\(multiplication.left) x \(multiplication.right) = \(multiplication.left * multiplication.right)")
// Prints "9 x 5 = 45"


참고둜 Observers와 WrappersλŠ” λ™μ‹œμ— μ‚¬μš©ν•˜μ§€ λͺ»ν•˜λŠ” κ²ƒμœΌλ‘œ 보인닀.

Can I implement a property observer in a property wrapper structure?

2. Setting Initial Values for Wrapped Properties

μœ„ μ½”λ“œλŠ” Property Wrappers κ°€ μ΄ˆκΈ°κ°’μ„ ν•˜λ“œμ½”λ”©ν•΄ μ €μž₯ν•˜κ³ μžˆλ‹€. λ”°λΌμ„œ λ‹€λ₯Έ μ΄ˆκΈ°κ°’μ„ 지정할 수 μ—†μ–΄ μœ μ—°μ„±μ΄ 떨어진닀.
μš°λ¦¬λŠ” 이 문제λ₯Ό Initializerλ₯Ό μ΄μš©ν•΄ ν•΄κ²°ν•  수 μžˆλ‹€.

μ‚¬κ°ν˜•μ˜ λ³€μ˜ 길이λ₯Ό μ •μ˜ν•˜λŠ” LengthOfSide κ°€ λ‹€μŒκ³Ό 같이 μ •μ˜λ˜μ–΄μžˆλ‹€.

@propertyWrapper
struct LengthOfSide {
    private var maximum: Int
    private var length: Int

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

    init() {
        maximum = 10
        length = 0
    }

    init(wrappedValue: Int) {
        maximum = 10
        length = min(wrappedValue, maximum)
    }

    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        length = min(wrappedValue, maximum)
    }
}
  • init() : arguments κ°€ 없이 μ΄ˆκΈ°ν™” ν•˜λ©΄ κΈ°λ³Έκ°’μœΌλ‘œ 졜고 κΈΈμ΄λŠ” 10, λ³€μ˜ 길이의 μ΄ˆκΈ°κ°’μ€ 0으둜 Structure λ₯Ό μ΄ˆκΈ°ν™”ν•œλ‹€.
  • init(wrappedValue:) : arguments λ₯Ό ν•˜λ‚˜λ§Œ λ°›μ•„ wrappedValue λ₯Ό λ³€μ˜ 길이의 μ΄ˆκΈ°κ°’μœΌλ‘œ ν•˜κ³  졜고 κΈΈμ΄λŠ” 10으둜 Structure λ₯Ό μ΄ˆκΈ°ν™”ν•œλ‹€.
  • init(wrappedValue:maximum:) : λ³€μ˜ 졜고 길이와 μ΄ˆκΈ°κ°’μ„ λͺ¨λ‘ λ°›μ•„ Structure λ₯Ό μ΄ˆκΈ°ν™”ν•œλ‹€.


  • init()
struct Rectangle {
    @LengthOfSide var height: Int
    @LengthOfSide var width: Int
}
var rectangle = Rectangle()
print(rectangle)
//Rectangle(_height: __lldb_expr_53.LengthOfSide(maximum: 10, length: 0),
//           _width: __lldb_expr_53.LengthOfSide(maximum: 10, length: 0))

init()을 μ΄μš©ν•΄ μ΄ˆκΈ°ν™”λ˜μ–΄ μ‚¬κ°ν˜•μ˜ μ΅œλŒ“κ°’μ€ 10, μ΄ˆκΈ°κ°’μ€ 0으둜 μ„€μ •λ˜μ—ˆλ‹€.

print("height: \(rectangle.height), width: \(rectangle.width)") // height: 0, width: 0

rectangle.height = 12
rectangle.width = 5
print("height: \(rectangle.height), width: \(rectangle.width)") // height: 10, width: 5

μ‚¬κ°ν˜•μ˜ 높이와 λ„ˆλΉ„λŠ” μ΄ˆκΈ°κ°’μ— μ˜ν•΄ 0μ΄μ—ˆκ³ , 높이λ₯Ό 12, λ„ˆλΉ„λ₯Ό 5둜 μ„€μ •ν–ˆλ‹€. ν•˜μ§€λ§Œ Property Wrappers 에 μ˜ν•΄ λ†’μ΄λŠ” 10으둜 μ΅œλŒ“κ°’μ„ λ„˜μ§€ μ•Šκ²Œ μˆ˜μ •λ˜μ—ˆλ‹€.


Property Wrappers λ₯Ό μ΄ˆκΈ°ν™” ν•˜λŠ” 방법은 두 가지가 μžˆλ‹€

1 ) init(wrappedValue:maximum:)

첫 번째 방법은 μœ„μ—μ„œ λ³Έ κ²ƒμ²˜λŸΌ Property Wrappers 의 Initializersλ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

struct NarrowRectangle {
    @LengthOfSide(wrappedValue: 15, maximum: 20) var height: Int
    @LengthOfSide(wrappedValue: 3, maximum: 5) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle)
//NarrowRectangle(_height: __lldb_expr_69.LengthOfSide(maximum: 20, length: 15),
//                 _width: __lldb_expr_69.LengthOfSide(maximum: 5, length: 3))

print("height: \(narrowRectangle.height), width: \(narrowRectangle.width)") // height: 10, width: 5

height λ₯Ό wrapping ν•œ LengthOfSide instance λŠ” Initializer LengthOfSide(wrappedValue: 15, maximum: 20)λ₯Ό ν˜ΈμΆœν•΄ μƒμ„±λ˜μ—ˆκ³ , weight λ₯Ό wrapping ν•œ LengthOfSide instance λŠ” Initializer LengthOfSide(wrappedValue: 3, maximum: 5)λ₯Ό ν˜ΈμΆœν•΄ μƒμ„±λ˜μ—ˆλ‹€.


2 ) Initial Values

또 λ‹€λ₯Έ λ°©λ²•μœΌλ‘œ, wrappedValueλ₯Ό Properties 의 Initial Valuesλ₯Ό μ‚¬μš©ν•΄ μ΄ˆκΈ°ν™”ν•˜λŠ” 것이닀.

struct HugeRectangle {
    @LengthOfSide(maximum: 20) var height: Int = 20
    @LengthOfSide(maximum: 20) var width: Int = 25
}
var hugeRectangle = HugeRectangle()
print(hugeRectangle)
//HugeRectangle(_height: __lldb_expr_74.LengthOfSide(maximum: 20, length: 20),
//               _width: __lldb_expr_74.LengthOfSide(maximum: 20, length: 20))

print("height: \(hugeRectangle.height), width: \(hugeRectangle.width)") // height: 20, width: 20

init(maximim:)μ΄λΌλŠ” Initializerκ°€ μ—†μŒμ—λ„ λΆˆκ΅¬ν•˜κ³ , init(wrappedValue:maximum:)κ³Ό λ™μΌν•˜κ²Œ μž‘λ™ν•¨μ„ μ•Œ 수 μžˆλ‹€.

3. Projecting a Value From a Property Wrapper

μš°μ„  Projection Mappingμ΄λΌλŠ” μš©μ–΄λ₯Ό μ•Œμ•„λ³΄μž.

ν”„λ‘œμ μ…˜ 맀핑(Projection Mapping)은 λŒ€μƒλ¬Όμ˜ ν‘œλ©΄μ— λΉ›μœΌλ‘œ 이루어진 μ˜μƒμ„ νˆ¬μ‚¬ν•˜μ—¬ λ³€ν™”λ₯Ό 쀌으둜써, ν˜„μ‹€μ— μ‘΄μž¬ν•˜λŠ” λŒ€μƒμ΄ λ‹€λ₯Έ 성격을 가진 κ²ƒμ²˜λŸΌ 보이도둝 ν•˜λŠ” κΈ°μˆ μ΄λ‹€.

Wikipedia - ν”„λ‘œμ μ…˜ 맀핑

즉, Projecting a Value From a Property Wrapper λŠ” Property Wrapperλ₯Ό μ΄μš©ν•΄ ν˜„μž¬μ˜ Instance 에 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 값을 μ‘΄μž¬ν•˜λŠ” λŒ€μƒμΈ κ²ƒμ²˜λŸΌ 보이도둝 ν•˜λŠ” κ²ƒμ΄λž€ 것을 μœ μΆ”ν•  수 μžˆλ‹€.


그리고 Apple Developer Documentation 에 projectedValue둜 검색을 ν•˜λ©΄ λ‹€μ–‘ν•œ κ³³μ—μ„œ μ‚¬μš©λ˜λŠ” 것을 λ³Ό 수 μžˆλŠ”λ°, λ‹€μŒ 두 링크(Link 1, Link 2)λ‘œλΆ€ν„° μœ μΆ”ν•΄λ³΄λ©΄

  • getter, setterλ₯Ό μ΄μš©ν•΄ μž‘λ™ν•œλ‹€
  • super μͺ½ valueλ₯Ό subμͺ½μ— λ…ΈμΆœμ‹œν‚¨λ‹€. 즉, 기본으둜 λ…ΈμΆœλ˜μ§€ μ•ŠλŠ” μƒμœ„ hierarchy의 정보λ₯Ό μ ‘κ·Όν•˜κ²Œ ν•œλ‹€

둜 μš”μ•½ν•  수 μžˆμ„ 것 κ°™λ‹€.

λ‹€μ‹œ Swift.org둜 λŒμ•„μ™€λ³΄μž. Property WrappersλŠ” wrappedValue 외에도 projectedValue μ •μ˜λ₯Ό μ΄μš©ν•΄ 좔가적인 κΈ°λŠ₯을 λ…ΈμΆœν•  수 μžˆλ‹€κ³  μ„€λͺ…ν•˜λŠ” 뢀뢄을 μ–΄λŠμ •λ„ 이해할 수 μžˆλ‹€.

Apple Developer Documentation 에 projectedValue λ₯Ό μ •μ˜ν•˜λŠ” 방법을 보면 μ–΄λ–€ Swift Library 그룹에 μ†ν•΄μžˆλŠ”μ§€μ— 따라 μ½”λ”© ν˜•νƒœκ°€ λ‹€λ₯Έ κ²ƒμœΌλ‘œ 보인닀. μš°μ„  Swift.org 의 예제λ₯Ό κΈ°μ€€μœΌλ‘œ μ„€λͺ…ν•˜λ©΄ Syntax λŠ” μ•„λž˜μ™€ κ°™λ‹€.


Syntax

@propertyWrapper
struct SomeStructure {
    private var someProperty: SomeType
    private(set) var projectedValue: Bool
    var wrappedValue: SomeType {
        get { someProperty }
        set { someProperty = newValue }
    }
}
  • Class, Structure, Enumerationλ₯Ό μ΄μš©ν•΄ μ •μ˜ν•˜λ©° 3가지 λΆ€λΆ„μœΌλ‘œ λ‚˜λ‰œλ‹€

  • @propertyWrapper Annotation 을 μ„ μ–Έ
  • private(set) var λ³€μˆ˜ μ„ μ–Έ
  • wrappedValue λΌλŠ” 이름을 κ°–λŠ” Computed Propertyλ₯Ό μ •μ˜


μœ„μ—μ„œ μ •μ˜ν•œ LengthOfSide 에 projectedValueλ₯Ό μΆ”κ°€ν•΄ λ‹€μ‹œ μ •μ˜ν•΄λ³΄μž.

@propertyWrapper
struct LengthOfSide {
    private var maximum: Int
    private var length: Int
    private(set) var projectedValue: Bool = false
    
    var wrappedValue: Int {
        get { length }
        set {
            if newValue > maximum {
                length = maximum
                projectedValue = true
            } else {
                length = newValue
                projectedValue = false
            }
        }
    }
    
    init() {
        maximum = 10
        length = 0
    }
    
    init(wrappedValue: Int) {
        maximum = 10
        length = min(wrappedValue, maximum)
    }
    
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        length = min(wrappedValue, maximum)
    }
}
struct HugeRectangle {
    @LengthOfSide(wrappedValue: 20, maximum: 20) var height: Int
    @LengthOfSide(maximum: 20) var width: Int = 25
}
var hugeRectangle = HugeRectangle()
print(hugeRectangle)
//HugeRectangle(_height: __lldb_expr_74.LengthOfSide(maximum: 20, length: 20),
//               _width: __lldb_expr_74.LengthOfSide(maximum: 20, length: 20))

print("height: \(hugeRectangle.height), width: \(hugeRectangle.width)") // height: 20, width: 20

HugeRectangle Structure λ‘œλΆ€ν„° μƒμ„±ν•œ hugeRectangle Instance λ₯Ό 좜λ ₯ν•΄λ³΄μ•˜μœΌλ‚˜ 기쑴의 LengthOfSide 와 λ‹€λ₯Όκ²Œ μ—†μ–΄ 보인닀.


print(hugeRectangle.height)     // 20
print(hugeRectangle.$height)    // false
print(hugeRectangle.width)      // 20
print(hugeRectangle.$width)     // false

ν•˜μ§€λ§Œ μ•žμ— $ 사인을 λΆ™μ—¬μ£Όμž Instance λ₯Ό μ •μ˜ν•  λ•Œμ—λ„ μ—†κ³ , 좜λ ₯ν•  λ•Œμ—λ„ μ—†λŠ” 값이 λ‚˜νƒ€λ‚œλ‹€.
이 값은 HugeRectangle 의 Properties κ°€ μ•„λ‹Œ LengthOfSide 의 Propertiesλ‹€!

ν•˜μ§€λ§Œ 마치 hugeRectangle Instance 의 Properties 인 것 처럼 νˆ¬μ˜λ˜μ–΄ 보여진닀!!

이제 wrappedValueλ₯Ό μ΄μš©ν•΄ 값을 μ΄ˆκ³Όν•˜λ„λ‘ μ €μž₯ν•΄λ³΄μž.

hugeRectangle.width = 30
print(hugeRectangle.width)      // 20
print(hugeRectangle.$width)     // true

값이 μ΄ˆκ³Όλ˜μ—ˆκ³ , setter에 μ •μ˜ν•œλŒ€λ‘œ width λŠ” maximum κ°’μœΌλ‘œ 보정해 μ €μž₯λ˜μ—ˆλ‹€. 그리고 projectedValueλŠ” true둜 λ³€κ²½λ˜μ—ˆλ‹€.

Projecting은 Initializers μ—μ„œλŠ” μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. @LengthOfSide(maximum: 20) var width: Int = 25 μ½”λ“œλ₯Ό 보면 마치 25 λΌλŠ” 값이 Property Wrapper 의 set 을 ν˜ΈμΆœν•΄ μž‘λ™ν•  것 κ°™μ§€λ§Œ 이것은 @LengthOfSide(wrappedValue: 25, maximum: 20)와 μ™„μ „νžˆ λ™μΌν•˜κ²Œ μž‘λ™ν•  뿐이닀. 즉, Instance κ°€ μƒμ„±λœ 이후 μ •μƒμ μœΌλ‘œ μž‘λ™ν•œλ‹€.


projectedValue λŠ” λ‹€μŒκ³Ό 같이 Class, Structure, Enumeration 의 λ‚΄λΆ€ contextμ—μ„œλ„ μ‚¬μš©ν•  수 μžˆλ‹€.

enum Size {
    case small, large
}

struct SizedRectangle {
    @LengthOfSide var height: Int
    @LengthOfSide var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}
var rectangle = SizedRectangle()
var resizeWasCalibrated = rectangle.resize(to: .small)

print(rectangle.height, rectangle.$height)  // 10 false
print(rectangle.width, rectangle.$width)    // 10 true
print(resizeWasCalibrated)                  // true

5. Global and Local Variables πŸ‘©β€πŸ’»

  • Global Variables: Functions, Methods, Closures, Type Context 외뢀에 μ •μ˜λœ λ³€μˆ˜λ₯Ό 의미
  • Local Variables: Functions, Methods, Closures Context 내뢀에 μ •μ˜λ˜ λ³€μˆ˜λ₯Ό 의미

1. Stored Variables

Stored VariablesλŠ” Stored Properties 처럼 값을 μ €μž₯ν•˜κ³  κ²€μƒ‰ν•˜λŠ” 것을 μ œκ³΅ν•œλ‹€.

Global Constants 와 Global Variables λŠ” 항상 lazilyν•˜κ²Œ κ³„μ‚°λœλ‹€. μ΄λŠ” Lazy Stored Properties 와 μœ μ‚¬ν•˜λ‹€. 단, Lazy Stored Properties 와 λ‹€λ₯Έ 점은 lazy modifier λ₯Ό 뢙일 ν•„μš”κ°€ μ—†λ‹€.

λ°˜λ©΄μ— Local Constants 와 Local Variables λŠ” μ ˆλŒ€ lazilyν•˜κ²Œ κ³„μ‚°λ˜μ§€ μ•ŠλŠ”λ‹€.

2. Computed Variables

Global Variables 와 Local Variables λͺ¨λ‘ Computedλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

3. Variable Observers

Global Variables 와 Local Variables λͺ¨λ‘ Observerλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

4. Variable Wrappers

Property WrappersλŠ” Local Stored Variablesμ—λ§Œ 적용 κ°€λŠ₯ν•˜λ‹€.
Global Variables λ˜λŠ” Computed Variables μ—λŠ” μ μš©ν•  수 μ—†λ‹€.

func someFunction() {
    @LengthOfSide var length: Int
    print(length)   // 0
    
    length = 5
    print(length)   // 5
    
    length = 12
    print(length)   // 10
}

someFunction()

6. Type Properties πŸ‘©β€πŸ’»

C λ‚˜ Objective-C μ—μ„œ static constants, static variables λ₯Ό μ •μ˜ν•˜κΈ° μœ„ν•΄ Global Static Variables λ₯Ό μ‚¬μš©ν–ˆλ‹€.

ν•˜μ§€λ§Œ Swift λŠ” λΆˆν•„μš”ν•˜κ²Œ μ „μ—­μœΌλ‘œ μƒμ„±λ˜λŠ” Global Static Variables 의 μ „μ—­ λ³€μˆ˜ μ˜€μ—Ό 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ Type Propertiesλ₯Ό μ œκ³΅ν•œλ‹€. Type Properties λŠ” Swift Types κ°€ μ •μ˜λ˜λŠ” { } λ‚΄λΆ€ context λ²”μœ„ 내에 μ •μ˜λ˜λ©°, κ·Έ Scope λ²”μœ„ λ‚΄μ—μ„œλ§Œ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.

1. Type Property Syntax

Global Static Variables 와 λ§ˆμ°¬κ°€μ§€λ‘œ Properties μ•žμ— static ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€.
단, Classes 의 경우 Computed Properties λ₯Ό Subclass μ—μ„œ overriding 을 ν—ˆμš©ν•˜λ €λ©΄ Superclass μ—μ„œ static keyword λŒ€μ‹  class keyword λ₯Ό μ‚¬μš©ν•œλ‹€.

Type PropertiesλŠ” μ •μ˜ν•  λ•Œ λ°˜λ“œμ‹œ Initiate Valueλ₯Ό ν•¨κ»˜ μ •μ˜ν•΄μ•Όν•œλ‹€.


  • Structures
struct SomeStructure {
    static var someTypeProperty = "Initiate Value"
    static var computedTypeProperty: Int {
        return 1
    }
}


  • Enumerations
enum SomeEnumeration {
    static var someTypeProperty = "Initiate Value"
    static var computedTypeProperty: Int {
        return 6
    }
}


  • Classes
class SomeClass {
    static var someTypeProperty = "Initiate Value"
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

computedTypeProperties λŠ” static keyword λ₯Ό μ‚¬μš©ν—Έμ§€λ§Œ overrideableComputedTypeProperty λŠ” class keyword λ₯Ό μ‚¬μš©ν•΄ Subclass μ—μ„œ overriding ν•˜λŠ” 것을 ν—ˆμš©ν–ˆλ‹€.

2. Querying and Setting Type Properties

1 ) Difference between Type Properties and Properties

μ•„λž˜μ™€ 같이 AnotherStructure λ₯Ό μ •μ˜ν–ˆλ‹€.

struct AnotherStructure {
    static var storedTypeProperty = "Apple"
    var storedProperty = "Pear"

    static var computedTypeProperty: Int { 1 }
    var computedProperty: Int { 10 }
}


  • Type Properties
print(AnotherStructure.storedTypeProperty)   // Apple
print(AnotherStructure.computedTypeProperty) // 1

AnotherStructure.storedTypeProperty = "Melon"
print(AnotherStructure.storedTypeProperty)   // Melon

Type Properties λŠ” Instance Properties 와 λ™μΌν•˜κ²Œ dot Syntaxλ₯Ό μ΄μš©ν•΄ 값에 μ ‘κ·Όν•˜κ³  값을 μ €μž₯ν•œλ‹€.


  • Instance Properties
var anotherStructure = AnotherStructure()
print(anotherStructure.storedProperty)       // Pear
print(anotherStructure.computedProperty)     // 10

anotherStructure.storedProperty = "Watermelon"
print(anotherStructure.storedProperty)       // Watermelon

Instance Properties λŠ” Instance 생성 μ „μ—λŠ” μ ‘κ·Όν•  수 μ—†λ‹€.


var theOtherStructure = AnotherStructure()
print(theOtherStructure.storedProperty)      // Pear

print(AnotherStructure.storedTypeProperty)   // Melon

μœ„μ—μ„œ anotherStructure κ°€ Instance Propertiesλ₯Ό μˆ˜μ •ν•œ 것은 theOtherStructure 에 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠλŠ”λ‹€. ν•˜μ§€λ§Œ AnotherStructure의 Type Propertiesλ₯Ό μˆ˜μ •ν•œ 것은 Type μžμ²΄κ°€ μˆ˜μ •λ˜μ—ˆκΈ° λ•Œλ¬Έμ— Apple 이 μ•„λ‹Œ Melon 을 좜λ ₯ν•œλ‹€.


2 ) Audio Channel Examples

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}
  • thresholdLevel : μ˜€λ””μ˜€κ°€ κ°€μ§ˆ 수 μžˆλŠ” λ³Όλ₯¨ μ΅œλŒ“κ°’μ„ μ •μ˜ (μƒμˆ˜ 10)
  • maxInputLevelForAllChannels : AudioChannel Instance κ°€ 받은 μ΅œλŒ€ μž…λ ₯값을 좔적(0μ—μ„œ μ‹œμž‘)
  • currentLevel : ν˜„μž¬μ˜ μ˜€λ””μ˜€ λ³Όλ₯¨μ„ 계산을 톡해 μ •μ˜


var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

쒌우 채널을 각각 Instnace 둜 μƒμ„±ν•œλ‹€.

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)     // 7
print(AudioChannel.maxInputLevelForAllChannels) // 7

μ™Όμͺ½ λ³Όλ₯¨μ„ 7둜 올리자 μ™Όμͺ½ μ±„λ„μ˜ λ³Όλ₯¨μ΄ 7둜, Type Property maxInputLevelForAllChannels μ—­μ‹œ 7둜 μ €μž₯λ˜μ—ˆλ‹€.

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)    // 10
print(AudioChannel.maxInputLevelForAllChannels) // 10

μ΄λ²ˆμ—” 였λ₯Έμͺ½ λ³Όλ₯¨μ„ 11둜 올리자 μ΅œλŒ€ 레벨 μ œν•œμ— μ˜ν•΄ 10으둜 μ €μž₯되고, 이에 따라 κ·Έ λ‹€μŒ if statement μ—μ„œ maxInputLevelForAllChannelsκ°€ 10으둜 μ €μž₯λ˜μ—ˆλ‹€.




Reference

  1. β€œProperties.” The Swift Programming Language Swift 5.7. accessed Nov. 21, 2022, Swift Docs Chapter 9 - Properties.
  2. β€œProjected Value.” Apple Developer Documentation. accessed Nov. 25, 2022, Apple Developer Documentation - Swift/Swift Standard Library/../projectedValue.
  3. β€œProjected Value.” Apple Developer Documentation. accessed Nov. 25, 2022, Apple Developer Documentation - Swift/Swift UI/../projectedValue.
  4. β€œν”„λ‘œμ μ…˜ 맀핑.” Wikipedia. Mar. 6, 2022, ν”„λ‘œμ μ…˜ 맀핑.