[Swift] 구조체와 클래스의 프로퍼티(Properties)
구조체와 클래스에서 프로퍼티의 저장은 각각 다르게 적용된다. 하나씩 살펴보자 📌 저장된 프로퍼티(Stored Properties) var (변수) 혹은 let(상수)를 통해 저장된 프로퍼티를 쓸 수 있다. 1️⃣ 구조체에서의 저장 프로퍼티 첫번째 요소와 길이 범위를 설정하는 프로퍼티를 갖고 있는 FixedLengthRange 구조체가 있다. FixedLengthRange 의 인스턴스는 1. firstValue 라는 저장된 프로퍼티 변수가 있으며 2. length 라는 저장된 프로퍼티 상수를 가진다. length 는 새 범위가 생성될 때 초기화되며 프로퍼티 상수 이므로 변경할 수 없다. struct FixedLengthRange { var firstValue: Int let length: Int } 생성한..
2023.02.13
no image
[Swift] 구조체와 클래스(Structures and Classes)
📌 구조체와 클래스 차이 구조체 클래스 공통점 1. 값을 저장할 수 있다. 2. 메소드를 정의할 수 있다. 3. 서브 스크립트 정의를 통해 값에 접근 가능 4. 초기화 상태 정의 가능 5. 기능적 확장 가능 6. 표준 프로토콜 준수 차이점 1. 타입을 추론하기 쉬움 2. struct 키워드로 시작 3. 값 타입(value Types) 임 1. class 키워드로 시작 2. 상속 사용 가능(한 클래스가 다른 클래스의 특성 상속 가능) 3. 타입 캐스팅을 사용하면 런타임 시에 클래스 인스턴스 확인 가능 4. 초기화 해제 구문 -> 클래스 인스턴스가 할당된 리소스 해제 가능 5. 자동 참조 카운팅을 통해 하나 이상의 인스턴스 참조를 허락함 6. 참조 타입(Reference Types) 임 단점 ) 복잡성 증가..
2023.02.08
320x100
728x90

 

구조체와 클래스에서 프로퍼티의 저장은 각각 다르게 적용된다. 

하나씩 살펴보자

 

 

 

📌 저장된 프로퍼티(Stored Properties)

var (변수) 혹은 let(상수)를 통해 저장된 프로퍼티를 쓸 수 있다. 

 

 

 

 

 

1️⃣ 구조체에서의 저장 프로퍼티

 

 

첫번째 요소와 길이 범위를 설정하는 프로퍼티를 갖고 있는 FixedLengthRange 구조체가 있다. 

 

FixedLengthRange 의 인스턴스는

1. firstValue 라는 저장된 프로퍼티 변수가 있으며

2. length 라는 저장된 프로퍼티 상수를 가진다.

 

 

 

length 는 새 범위가 생성될 때 초기화되며 프로퍼티 상수 이므로 변경할 수 없다.

 

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

 

 

 

 

생성한 구조체를 사용해 보자.

구조체 초기화 구문을 통해 설정한 값들을 rangeOfItems 변수에 할당한 다음, 

firstValue 프로퍼티를 6으로 변경하였다. 

 

var rangeOfItems = FixedLengthRange(firstValue: 0, length: 3)
//0,1, and 2

rangeOfItems.firstValue = 6
// 6, 7, and 8

 

 

 

 

 

만약에 상수에 구조체를 할당하면 !

구조체의 프로퍼티가 변수로 선언되어 있어도, 인스턴스의 프로퍼티를 변경할 수 없다

 

let rangeOfItems = FixedLengthRange(firstValue: 0, length: 4)
// 상수로 선언한 뒤, 인스턴스 생성 

rangeItems.firstValue = 6
// rangeItems는 상수이므로 프로퍼티 수정 불가

 

 

 

왜 그럴까🤔??

 

👉 구조체값 타입이기 때문이다. 

 

 

 

값 타입의 인스턴스는 상수로 선언되면, 모든 프로퍼티에 대해 수정이 불가능하다. 

( let notChange = FixedLengthRange()   -> notChange의 프로퍼티 수정 불가)

 

 

 

✅ 하지만 클래스는 참조 타입이라 다르게 동작한다. 

클래스는 참조 타입의 인스턴스를 상수로 선언해도, 인스턴스의 프로퍼티 변수에 대해 변경이 가능하다. 

 

 


 

 

2️⃣ 늦은 저장 프로퍼티(Lazy Stored Properties)

 

 

  • 프로퍼티 선언 전에 lazy 수정자를 붙인다.
  • lazy가 붙은 프로퍼티는 처음 사용될 때 까지, 그 초기값이 계산되지 않는다.
  • 외보 요인에 의해 초기값이 달라질 때 or 초기값이 필요할 때까지 수행되면 안되는 작업이 있을 때 사용하면 유리 

 

 

 

 

주의📢

lazy를 사용할 때는 프로퍼티를 var 키워드를 사용하여 변수로 선언해야 함

➡️ 인스턴스 초기화가 완료된 이후에도 초기값이 없을 수 있기 때문이다. (상수는 초기화가 완료되기 전에 항상 값을 갖고 있어야 함 👉 즉, lazy로 선언할 수 없음)

 

 

 

class 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")

 

 


 

3️⃣ 계산된 프로퍼티 (Computed Properties)

 

 

값을 실질적으로 저장하는 것이 아니라, 다른 프로퍼티와 값을 간접적으로 조회하고 설정하는 getter와 setter를 제공함 

 

 

아래 예시에서 Point와 Size는 값을 저장하는 프로퍼티들을 갖고 있지만,

Rect 구조체는 center라는 계산된 프로퍼티를 가진다. Rect이 반환하는 x와 y 값의 포인트는 명시적인 값으로

저장되는 것이 아니라, origin과 size로 계산되어 결정된다. 

 

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
// 사각형 중앙의 값은 (5.0, 5.0)

 

 

square라는 변수를 통해 새로운 Rect 변수를 생성하고 원점은 (0,0)으로 너비는 (10,10)으로 설정한다

 

 

 

 

👉 그렇다면 square.center 결과값은??

 

 

center는 getter를 호출하여 현재 프로퍼티 값을 조회한 다음( (0,0) , (10,10)) 사각형의 중심 값을 계산한다. 

그리고 Point 리턴값인 (5,5)를 결과로 반환한다. 

 

 

 

 

 

 

아예 center 프로퍼티의 값을 새롭게 설정했다면 어떻게 될까

중심점을 (15.0, 15.0)으로 설정하면 center의 setter가 호출되어 origin 프로퍼티에 저장된 x값과 y값을 변경하고

변경된 중점으로 사각형을 위치시킨다. 

 

square.center = Point(x: 15.0, y: 15.0)


print("square.origin 값은 x는 \(square.origin.x)  y는 \(square.origin.y)")
//  x는 10.0   y는 10.0

 


 

4️⃣ 짧은 Setter 선언

 

 

setter 설정값 이름을 정의하지 않았다면, newValue 라는 기본 이름이 사용된다. 

 

struct AlternativeRect {
    ...
    var center: Point {
        ...
        
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

 

 


 

 

5️⃣ 짧은 Getter 선언

 

 

기존 getter에서 return이 생략되었으며, 계산 식을 x, y 안에 포함한다. 

 

struct CompactRect {
    ...
    var center: Point {
    
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        
        ...
    }
}

 


 

6️⃣ 읽기 전용 계산된 프로퍼티

 

 

setter가 없고 getter 만 있는 것읽기 전용 계산 프로퍼티(아래 Cuboid 구조체에서 volume 프로퍼티에 해당)라 한다. 

 

 

  • 항상 값을 반환함
  • 점 구문으로 접근 가능
  • 다른 값은 설정 불가 (읽기 전용)
  • get 키워드와 중괄호를 삭제하여 간편하게 선언 가능 

 

 

 

 

주의📢

구조체 안 프로퍼티는 상수로 선언하면 안된다. 값이 고정되어 있지 않기 때문에, var 변수로 선언해야 하며

인스턴스 초기화 부분에서 한 번 설정 시 값이 변경될 수 없으므로 이 때 let 키워드를 사용한다. 

 

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("fourByFiveByTwo 의 부피는 \(fourByFiveByTwo.volume)")
// Prints fourByFiveByTwo 의 부피는 40.0

 


 

7️⃣ 프로퍼티 관찰자(Property Observers)

 

 

  • 프로퍼티의 값이 변경되는 지 관찰하고 응답함
  • 새로운 값이 현재 값과 같더라도 호출
  • 상속에서 하위 클래스의 프로퍼티를 재정의하여 프로퍼티 관찰자를 추가함

 

 

 

 

프로퍼티 관찰자 정의 방법

  willSet didSet
특징 - 값이 저장되기 직전에 호출
- 새로운 프로퍼티 값이 전달됨
- 새로운 값이 저장되자 마자 호출
- 예전 프로퍼티 값을 포함한 상수 파라미터가 전달됨 

 

 

 

willSet과 didSet의 예시를 살펴보자

 

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("totalSteps는 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("\(totalSteps - oldValue) 만큼의 값이 증가")
            }
        }
    }
}

 

 

willSet은 새로운 값을 위한 newTotalSteps 파라미터 명을 사용한다. 

didSet은 totalSteps 값이 업데이트 되고 난 후에 호출된다. 

오래된 값에 대해 oldValue 기본 이름을 사용하고, 새로운 값 totalSteps와 비교한다. 

 

 

let stepCounter = StepCounter()

stepCounter.totalSteps = 200
// totalSteps는 200
// 200 만큼의 값이 증가


stepCounter.totalSteps = 360
// totalSteps는 360
// 160 만큼의 값이 증가


stepCounter.totalSteps = 896
// totalSteps는 896
// 536 만큼의 값이 증가

 


 

8️⃣ 프로퍼티 래퍼(Property Wrappers)

 

 

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

 

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

 

TwelveOrLess 구조체는 래핑하는 값이 항상 12와 같거나 더 작은 숫자만을 가지도록 설정된 구조체이다. 

setter를 통해 12보다 작은 값을 설정하고, 설정된 값을 getter를 통해 반환한다. 

 

 

 

이렇게 설정한 프로퍼티 래퍼는 속성으로 사용가능하며, 프로퍼티 전에 래퍼의 이름을 작성하여 적용하면 된다. 

 

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

 

 

 

height와 width 프로퍼티는 항상 12이하의 값을 가지는 지 TwelveOrLess  프로퍼티 래퍼를 통해 확인한다.

 

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

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

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

 

 

rectangle의 높이(rectangle.height)의 초기값은 number = 0 으로 초기화 되어 있기 때문에 0이다.

이때, .height을 10으로 저장하면 12보다 작기 때문에 성공적으로 저장되지만,

24를 저장하면 12보다 크기 때문에 실질적으로 12가 저장된다. 

 

 

 

 

다르게 프로퍼티 래퍼 사용하는 방법

더보기

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

 


 

 

9️⃣ 프로퍼티 래퍼 초기값 설정하기

 

 

위 프로퍼티 래퍼는 초기값 지정이 불가능하다.

 

 

초기값을 설정하기 위해서는 다음과 같이  init() 을 통해 정의할 필요가 있다. 

아래의 SmallNumber 구조체는

init(), init(wrappedValue:), init(wrappedValue:maximum:) 의 3개의 초기화를 가지고 있다. 

 

@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)
    }
    
}

 

이를 사용해보자.

 

 

1. init() 사용하기

 

기존의 프로퍼티 래퍼를 사용하는 방법과 동일하게 프로퍼티 변수 앞에 선언해주면 된다. 

 

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

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

 

 

 

2. init(wrappedValue:) 사용하기

 

height와 width 값을 원하는 값으로 저장해주면 된다.

height 와 width 에 1을 저장해주면 SmallNumber의 인스턴스가 SmallNumber(wrappedValue: 1) 호출로 생성된다.

이로인해 wrappedValue 값으로 1을 사용하고 기본 최대값으로 12를 사용한다. 

 

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

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

 

 

 

 

3. init(wrappedValue:maximum:) 사용하기

init(wrappedValue:maximum:)  형태에 smallNumber 래퍼를 씌워 wrappedValue와 maximum 값을 설정하면 된다. 

 

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

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

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// 5 4

 

 

 

4. 각 프로퍼티에 서로 다른 초기화 적용하기

 

 

height에는 init(wrappedValue:) 초기화를 호출하고, width에는 init(wrappedValue:maximum:)를 호출해보자

width를 래핑한 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9) 를 호출한다. 

 

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"

 

 

 

 

 

 

728x90
반응형
320x100
728x90

 

📌 구조체와 클래스 차이

  구조체 클래스
공통점 1. 저장할 수 있다.

2. 메소드를 정의할 수 있다.

3. 서브 스크립트 정의를 통해 값에 접근 가능

4. 초기화 상태 정의 가능

5. 기능적 확장 가능 

6. 표준 프로토콜 준수 
차이점 1. 타입을 추론하기 쉬움

2. struct 키워드로 시작 

3. 값 타입(value Types) 임 
1. class 키워드로 시작 

2. 상속 사용 가능(한 클래스가 다른 클래스의 특성 상속 가능)

3. 타입 캐스팅을 사용하면 런타임 시에 클래스 인스턴스 확인 가능

4. 초기화 해제 구문 -> 클래스 인스턴스가 할당된 리소스 해제 가능

5. 자동 참조 카운팅을 통해 하나 이상의 인스턴스 참조를 허락함  

6. 참조 타입(Reference Types) 임

단점 ) 복잡성 증가 

 

 

 

 

구조체와 클래스 생성 정의 

 

1. 구조체와 클래스의 이름UpperCameCase를 사용한다(첫 시작 대문자)

2. 프로퍼티lowerCameCase를 따른다. (소문자 시작) 

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

 

 


 

1️⃣ 인스턴스 생성

 

let someResolution = Resolution()
let someVideoMode = VideoMode()

 


 

2️⃣ 프로퍼티 접근

 

Resolution의 width 프로퍼티에 접근하고 싶다면, 

인스턴스 뒤에 구분자(.)를 통해 접근하면 된다. 

print("너비는 \(someResolution.width)")
// Prints 너비는  0

 

VideoMode는 Resolution 구조체를 프로퍼티로 가지고 있다

VideoMode에서 Resolution 구조체의 width 서브 프로퍼티에 접근하려면 연쇄 접근하면 된다.

someVideoMode 인스턴스 -> resolution 프로퍼티 -> width 서브 프로퍼티 

print("someVideoMode의 너비 \(someVideoMode.resolution.width)")
// Prints someVideoMode의 너비 0

 

 


 

3️⃣ 구조체 멤버 초기화

 

구조체는 멤버별 초기화 구문을 다음과 같이 갖고 있지만, 클래스 인스턴스는 멤버별 초기화를 받지 않는다

let vga = Resolution(width: 640, height: 480)

 


 

4️⃣ 구조체의 값 복사 

 

hd 상수를 선언하여 Resolution 인스턴스를 초기화하여 설정한다음, 

cinema라는 변수에 hd 값을 할당한다. 

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

 

 

hd와 cinema는 현재는 같은 값을 가지지만, 둘은 완벽히 다른 인스턴스 이다. 

 

cinema의 width를 변경해보자.

cinema의 width가 2048으로 변경되어 출력될 것이다. 

cinema.width = 2048

print("cinema is now \(cinema.width) pixels wide")
 // cinema is now 2048 pixels wide

 

 

하지만 기존의 hd의 width는 변경되지 않고 1920 기존 값을 유지한다. 

다음과 같이 둘은 완벽히 다른 인스턴스이다. 

 

 

 

 

 

 

열거형에서도 같이 동작된다. 

다음과 같은 열거형이 있으며 current와 remembered 변수 상수를 통해 값 할당과 복사를 했다고 하자.

// 열거형 선언 
enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}

var current = CompassPoint.west    //프로퍼티 할당
let remembered = currentDirection  // current 복사

 

 

이 후, current만 CompassPoint의 turnNorth 함수를 호출해본다. 

current.turnNorth()

 

 

아마 값이 north로 변경 되었을 것이다.

그렇다면 remembered는? 

 

 

 

 

이제 current의 값과 remembered의 값을 확인해보자.

print("current의 방향은 \(currentDirection)")
print("remembered의 방향은 \(rememberedDirection)")
// Prints current의 방향은north
// Prints remembered의 방향은 west

remembered는 current의 복사본으로 current의 값이 변경되어도 복사본에는 영향을 주지 않기 때문에

기존 할당값인 west로 유지된다. 

 

 

 

이를 통해 구조체열거형값 타입임을 알 수 있다. 

값 타입 (value type) 은 변수 또는 상수에 할당될 때나 함수에 전달될 때 복사 되는 값인 타입

 

 

 


 

 

 

 

 

🤔 그렇다면 클래스는?

 

 

클래스참조 타입(Reference Types)

참조 타입 (reference types) 은 변수 또는 상수에 할당될 때나 함수로 전달될 때 복사되지 않음

 

 

 

위 VideMode 클래스를 다시 살펴보자 

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

 

 

그리고 tenEighty 상수에 VideoMode 인스턴스를 할당하고 그 프로퍼티 속성 값을 다음과 같이 설정한다.

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

 

 

 

그리고 또 다른 상수 alsoTenEighty에 tenEighty를 할당해보자

그리고 나서 alsoTenEighty의 frameRate 값을 30.0으로 바꾸면 tenEighty의 frameRate 프로퍼티는 어떻게 될까??

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

 

 

 

🔎 결과)

print("tenEighty의 frameRate은 \(tenEighty.frameRate)")
// Prints tenEighty의 frameRate은 30.0

tenEighty.frameRate의 값 또한 30.0으로 변경된 것을 볼 수 있다. 

 

 

 

왜😮??

tenEighty와 alsoTenEighty는 같은 VideoMode 인스턴스를 참조하기 때문이다. 

 

 

 

클래스는 구조체와 열거형과는 다르게 값 저장이 아닌, 값 참조이기 때문 

 

 


 

 

5️⃣ 식별 연산자

 

  • 동일 인스턴스 (Identical to) (===)
  • 동일하지 않은 인스턴스 (Not identical to) (!==)
  •  
    동일 인스턴스는(3개의 등호로 표시, ===)  같음(equal to, 2개 등호, ==)과 같다는 말은 아니다. 
     
     
동일 인스턴스 (3개의 등호로 표시, ===)  래스 타입의 상수 또는 변수가 동일한 클래스 인스턴스를 참조함
같음(2개 등호, ==)  두 인스턴스 값이 동일하거나 동등함 

 

 

 

 

 

728x90
반응형