ios/문법

[iOS, Swift] Property(프로퍼티)

2023. 5. 2. 02:52
목차
  1. 1. Stored Property
  2. 2. Computed Property
  3. 3. Type Property
  4. 4. Property Observer(프로퍼티 옵저버 란?)

Porperty

프로퍼티란 클래스, 구조체, 열거형에서 소속된 변수 및 속성(Attributes)등을 불리우는 개념입니다. 프로퍼티의 종류는 크게 아래와 같이 분류할 수 있습니다.

  1. Stored Property : 저장 프로퍼티
  2. Computed Property : 연산 프로퍼티
  3. Type Property : 타입 프로퍼티

1. Stored Property

1.1 Stored Property(저장 프로퍼티)

  • 저장 프로퍼티는 변수(var)나 상수(let)를 클래스나 구조체의 인스턴스의 한 부분으로 저장 하는 것을 말합니다.
  • 객체가 생성이 되면 자동적으로 초기화됩니다.
  • 클래스, 구조체에서는 지원되지만, 열거형(Enum)에는 지원되지 않습니다.
  • var로 선언하면 “변수”를 저장합니다
  • let으로 선언하면 “상수”를 저장하고, 선언 이후 변경 불가합니다.
  • Stored Property는 사용 시점에 따라서 Lazy Stored Property라는 것도 있습니다.

클래스로 선언된 저장프로퍼티

import UIKit

class PersonInfoClass {
    let name: String
    var age: Int
}
let Person = PersonInfoClass(name: "John", age: 18)
Person.age = 19
print(Person)

 

Person이란 인스턴스를 let, 즉 “상수”로 선언 했는데, age란 저장 프로퍼티가 var라 해도 변경할 수 있는 이유

 

 

클래스는 참조타입이기 때문에 위의 그림과 같이 지역 상수 Person은 스택에 할당 되고 실제 PersonInfoClass
인스턴스는 힙에 할당 됩니다.
스택에 있는 John(Person)은 힙 영역에 있는 인스턴스를 참조하고 있는 형태입니다. 따라서 Person 안에는 힙에 할당된 인스턴스의 주소값이 저장되어있습니다.
즉, Person이란 클래스 인스턴스를 let으로 생성한다는 것은 실제 힙 영역에 저장된 저장 프로퍼티 name, age와는 상관 없이 스택 영역에 저장된 Person 안의 주소값이 상수로 설정된다는 것을 의미합니다.

 

 

구조체로 선언된 저장프로퍼티

import UIKit

struct PersonInfoStruct {
    let name: String
    var age: Int
}
var John = PersonInfoStruct(name: "John", age: 18)
John.age = 19
print(John)

Person이라는 구조체 인스턴스를 let으로 할당할 경우, 구조체는 값타입이므로 구조체의 저장프로퍼티들도 모두 스택 영역에 할당됩니다. 따라서 변경 할 수 없습니다.

 

 

1.2 Lazy Stored Property(지연 저장 프로퍼티)

  • 프로퍼티가 호출되기 전까지는 선언만 될 뿐 초기화되지 않고 있다가, 프로퍼티가 호출되는 순간에 초기화 되는 저장 프로퍼티
  • 즉, 값이 사용되기 전까지는 연산되지 않는다.(사용되는 시점에 메모리에 값을 올립니다.)
  • lazy라는 키워드를 사용하여 선언
  • 최초 값을 인스턴스 초기화가 끝날때까지 갖을 수 없기때문에 지연 저장 프로퍼티는 항상 변수(var)를 사용해 줘야 합니다. 상수(let) 프로퍼티들은 초기화가 끝나기 전에 값이 있어야하기 때문에 지연(lazy)로 선언 될 수 없습니다.
class DataImporter {
  //DataImporter for getting an external file

  var filename = "data.txt"
    init() { print("DataImporter init") }
}

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

let manager = DataManager()
manager.data.append("Some Data") //DataImporter 초기화되지않음
manager.data.append("Some more data") //DataImporter 초기화되지않음
manager.importer.filename = "test.txt" //DataImporter 초기화 -> print("DataImporter init")

지연 프로퍼티를 사용하면 인스턴스의 초기화 시 불필요한 모든 저장프로퍼티를 초기화 하지 않아도 됩니다. 그러므로, 불필요한 메모리 낭비를 방지할 수 있습니다.

 

 

2. Computed Property

  • 클래스, 구조체, 열거형에서 모두 사용 가능합니다
  • 저장 공간을 갖지 않고, 다른 “저장 프로퍼티”의 값을 읽어 연산을 실행하거나, 프로퍼티로 전달받은 값을 다른 프로퍼티에 저장합니다.
    • 항상 var로 선언되어야 합니다.
    • 반드시 연산 프로퍼티를 위한 저장 프로터티가 하나 있어야 합니다.
  • 실제 값을 가지고 있는 것이 아니라, getter, setter등을 통해서 값을 설정하고 전달해줍니다.
    • getter: 어떤 저장 프로퍼티의 값을 연산해서 return할 것인지, return 구문이 항상 존재해야 합니다.
    • setter: 파라미터로 받은 값을 어떤 저장 프로퍼티에 어떻게 설정할 것인지를 구현합니다.
  • 연산 프로퍼티는 어떠한 값을 저장하는 것이 아니기 때문에, 타입 추론을 통해 형식을 알 수 없어서, 반드시 선언할 때 타입 어노테이션을 통해 자료형을 명시해야합니다.
  • get, set 동시에 구현 가능하며, get만 구현하는 것도 가능하지만 set만 구현하는것은 불가능합니다.
  • set에서 파라미터를 생략할 수 있으며, newValue 키워드를 사용합니다.
  • 기존 언어의 Getter, 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 {  //var로 선언, 타입 명시
    get {
      let centerX = origin.x + (size.width / 2)
      let centerY = origin.y + (size.height / 2)
      return Point(x: centerX, y: centerY)
    }
    set(newCenter) { //파라미터 생략가능(newValue 사용)
      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
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)

 

get-only

class sayHi {
    var name: String = ""

    var plusHi: String {
        get {   //get 생략 가능
            return self.name + " Hi!"
        }
    }
    //값을 리턴하지만 저장하지는 않음
}

 

 

3. Type Property

  • 객체 자체에 관련된 값을 다뤄야할때, 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장하는 것을 타입 프로퍼티라고 합니다.
    • 타입프로퍼티는 인스턴스에 속한 값이 아닌 클래스나 구조체 자체에 속한 값이므로 인스턴스를 생성하지 않고, 클래스나 구조체 자체에 저장하며, 저장된 값은 모든 인스턴스가 공동으로 사용할 수 있습니다.(전역변수와 같은 느낌)
    • 이 값은 복사된 값이 아니라 실제로 하나의 값이므로 하나의 인스턴스에서 타입프로퍼티의 값을 변경하면 나머지 인스턴스들이 일괄적으로 변경된 값을 적용받습니다.
  • 타입 프로퍼티는 저장 타입 프로퍼티와 연산 타입 프로퍼티가 있습니다.
  • static 키워드를 사용하여 정의합니다.
  • 모든 타입이 공통적인 값을 정의하는 데 유용하게 사용됩니다(ex 싱글톤..)

3.1 Stored Type Property(저장 타입 프로퍼티)

  • 변수와 상수 모두 사용가능하지만, 반드시 초깃값을 설정해야합니다.
  • 다중스레드 환경에서도 무조건 한 번만 초기화된다는 보장을 받기 때문이다.

3.2 Computed Type Property(연산 타입 프로퍼티)

  • 저장 타입 프로퍼티처럼 인스턴스에 접근하지 않고 타입 이름만으로 사용이 가능합니다.
  • 연산 프로퍼티와 마찬가지로 값을 저장하는게 아니므로 var로만 정의가 가능합니다.
  • 연산 타입 프로퍼티는 Subclass에서 오버라이딩이 가능합니다.(class 키워드 사용)
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
      }
}

class ChildSomeClass : SomeClass{
      // 슈퍼클래스의 특정 타입 프로터티를 재정의 가능
      override static var overrideableComputedTypeProperty: Int{
               return 2222
      }
}

// 별도의 인스턴스 생성없이 바로 ‘.’을 통해서 프로퍼티 접근 가능

SomeStructure.storedTypeProperty = "Another value."

print(SomeStructure.storedTypeProperty) // Prints "Some value."
print(SomeStructure.storedTypeProperty) // Prints "Another value."
print(SomeEnumeration.computedTypeProperty) // Prints "6"
print(SomeClass.computedTypeProperty) // Prints "27"

4. Property Observer(프로퍼티 옵저버 란?)

  • 새값이 설정될 때 해당 이벤트를 감지할 수 있는 옵저버를 제공
  • 프로퍼티 옵저버는 새 값이 이전 값과 같더라도 항상 호출
  • 지연 저장 프로퍼티에서는 사용할 수 없음
  • willSet: 값이 저장되기 바로 직전에 호출됨
  • didSet: 새 값이 저장되고 난 직후에 호출됨

저장프로퍼티에 추가된 프로퍼티 옵저버

var name: String = "Unknown" {
    willSet(newName) { //파라미터 생략 가능(newValue)
        print("현재 이름 = \(name), 바뀔 이름 = \(newName)")
    }
    didSet(oldName) { //파라미터 생략 가능(oldValue)
        print("현재 이름 = \(name), 이전 이름 = \(oldName)")
    }
}
name = "James"

연산 프로퍼티에 추가된 프로퍼티 옵저버

  • 부모 클래스의 연산 프로퍼티를 오버라이딩 할 경우 프로퍼티 옵저버를 추가할 수 있습니다.
class Human {
    var name = "Unknown"
    var alias: String {
        get {
            return name + "(현재이름)"
        }
        set {
            name = newValue + "(별명)"
        }
//        willSet { }       // error! 'willSet' cannot be provided together with a getter
//        didSet  { }       // error! 'didSet' cannot be provided together with a getter
// setter를 통해 값 변경을 감지할 수 있으므로 프로퍼티 옵저버를 만들수 없습니다.
    }
}


//alias란 부모 클래스의 연산 프로퍼티를 "오버라이딩"하면, 연산 프로퍼티에 프로퍼티 옵저버를 추가해서 사용할 수 있습니다.
class Man: Human {
    override var alias: String {
        willSet {
            print("현재 alias = \(alias), 바뀔 alias = \(newValue)")
        }
        didSet {
            print("현재 alias = \(alias), 바뀌기 전 alias = \(oldValue)")
        }
    }
}

let man: Man = .init()
man.alias = "James Dean"
  1. 1. Stored Property
  2. 2. Computed Property
  3. 3. Type Property
  4. 4. Property Observer(프로퍼티 옵저버 란?)
chobo5
chobo5
개발 기록
전체
오늘
어제
chobo5
chobo5's blog
chobo5
  • 분류 전체보기
    • ios
      • 문법
      • UIKit
      • UI구현
      • 기타
    • IT
    • Java
      • 기초
      • Spring
      • JPA
    • Algorithm
    • OOP
    • SQL

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • restapi
  • property
  • Swift
  • setter
  • didset
  • API
  • Lazy
  • static
  • getter
  • 프로퍼티

최근 글

hELLO · Designed By 정상우.
chobo5
[iOS, Swift] Property(프로퍼티)
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.