21. 10. 19 저장프로퍼티, 연산프로퍼티, init, 중첩타입

Allie·2021년 11월 24일
0

TIL

목록 보기
3/14

Properties

프로퍼티는 값을 특정 클래스(class), 구조체(struct), 열거형(enum)과 연결한다.

  • Swift의 Properties
    • Stored Property(저장 프로퍼티)
    • Computed Property(연산 프로퍼티)
    • Type Property(타입 프로퍼티)

Stored Property 저장 프로퍼티

  • 저장 프로퍼티는 상수와 변수의 값을 인스턴스의 일부로 저장한다.
  • 클래스와 구조체에서만 사용 됨!
  • var → 변수를 저장 / let → 상수를 저장 ( 선언할 때 저장할 기본값을 줄 수 있음. 수정도 가능)
struct FixedLengthRange {
    var firstValue: Int      // 변수 저장 프로퍼티
    let length: Int          // 상수 저장 프로퍼티
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2

/* rangeOfThreeItems = FixedLengthRange의 인스턴스
구조체는 기본적으로 저장프로퍼티들을 파라미터로 가지는 이니셜라이저가 있다.
초기값을 주면 FixedLengthRange()로 선언해도 됨. */

rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

/* firstValue는 변수 저장 프로퍼티로 선언되어서 값을 변경할 수 있지만,
length는 상수 저장 프로퍼티로 선언되어 값을 변경할 수 없다. */

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property

/* 구조체는 값 타입이기 때문에, 구조체의 인스턴스가 var로 선언되면 해당하는 구조체의
변수 저장 프로퍼티들을 변경할 수 있지만, let으로 선언된다면 해당 구조체의 모든 프로퍼티가
let으로 선언된 것과 같은 의미다. */
  • 참조 타입인 클래스에서는?
class FixedLengthRange {
    var firstValue: Int
    let length: Int

    init(firstValue : Int, length:Int) {      // 저장 프로퍼티에 초기값이 없으면,
        self.firstValue = firstValue          // 클래스에서는 반드시 init이 필요하다.
        self.length = length
    }
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
rangeOfThreeItems.firstValue = 3
rangeOfThreeItems.length = 10       // error
// 이건 클래스에서도 안됨.

let rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
rangeOfThreeItems.firstValue = 3
rangeOfThreeItems.length = 10       // error

/* rangeOfThreeItems를 let으로 선언했을 때, 클래스에서는 var로 선언되었던 firstValue의
값을 변경할 수 있다. 클래스는 참조 타입이기 때문에, FixedLengthRange에 바로 접근한다.
FixedLengthRange에서 firstValue는 var로 선언되었기 때문에 새로운 인스턴스가 let으로
생성되었다 할지라도, 값을 변경할 수 있지만 length는 원래도 let으로 선언되어서 변경할 수 없다. */
  • Lazy Stored Properties
    • 값이 사용되기 전까지는 값이 계산되지 않는 프로퍼티를 말한다.

    • lazy 키워드를 사용해 선언한다. 초기값은 인스턴스 초기값이 검색되지 않을 수 있기 때문에 항상 변수로 선언해야 한다. let으로 선언한 프로퍼티는 초기화를 함과 동시에 값을 가져야해서 lazy로 선언 할 수 없다. Lazy Stored Property는 값이 필요할 때 초기화를 하기 때문!

    • 초기값이 인스턴스의 초기화가 될 때까지 값을 모르는 외부요소에 의존하는 경우나 초기값이 복잡하거나 계산 비용이 많이 드는 설정을 필요로 할 때에 유용하다.

    • Lazy Stored Property를 잘 사용하면 성능도 올라가고 공간낭비도 줄일 수 있다.

      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
      }
      
      /* importer는 DataImporter의 인스턴스이다. */
      
      let manager = DataManager()
      // DataManager의 저장프로퍼티들은 초기값이 있어서 init이 필요하지 않음.
      
      manager.data.append("Some data")
      manager.data.append("Some more data")
      // the DataImporter instance for the importer property hasn't yet been created
      /* DataImporter의 인스턴스인 importer 프로퍼티는 lazy 저장 프로퍼티라서 
      아직 생성되지 않았음. */
      
      print(manager.importer.filename)
      /* importer 프로퍼티에 처음 액세스 할 때 만들어진다. DataManager인스턴스인 manager는 
      파일에서 데이터를 가져오지 않고도 데이터를 관리할 수 있다. DataManager 인스턴스인 manager를 
      만들 때 DataImporter 인스턴스를 만들 필요가 없음. 대신 DataImporter 인스턴스를 처음
      사용할 때 생성하는 것이 더 합리적이다. */

연산 프로퍼티

  • 연산프로퍼티는 다른 저장 프로퍼티의 값을 읽어서 특정한 연산을 통해 값을 리턴해준다. (값을 저장하지 않음) 그래서 항상 변수var 로 선언되어야 한다.
  • 클래스, 구조체, 열거형에서 사용된다. 해당 타입에 값을 저장할 저장 프로퍼티가 하나 있어야한다. 연산 프로퍼티는 자기 자신을 리턴하거나 값을 지정할 수 없기 때문!
  • 읽기 전용으로 구현할 수 있지만, 쓰기 전용으로는 구현할 수 없다.
  • 읽기 전용으로 구현할 때, get블럭만 작성해도 되지만, 생략도 가능하다.
  • 읽기, 쓰기 모두 가능하게 할때는 getset 모두 구현해야 한다.
  • set에서 암시적 매개변수 newValue를 사용할 수 있다.
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, set이 있는걸 보아 center는 연산 프로퍼티이다.
        get {       // get에서도 변수 선언 및 값 할당이 가능하다. 리턴만 제대로 하기!
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)// 리턴 타입은 center 타입과 같아야 함.
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }  // newCenter 대신에 newValue로 넣어줘도 된다.
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center

/* center라는 프로퍼티가 (5, 5)라는 값을 가지고 있는 게 아닌,
구조체 Point의 x와 y가 5라는 값을 가지고 있는 것이다. */

square.center = Point(x: 15.0, y: 15.0)
// 이 코드가 실행되는 순간 center의 set이 호출된다. 

print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

Type Property 타입 프로퍼티

  • 프로퍼티를 타입 자체와 연결 한다.
  • 나중에 공부하자....

Initialization

  • 클래스와 구조체는 해당 타입의 인스턴스가 생성될 때까지 모든 저장 프로퍼티를 적절한 초기값으로 설정해야한다. 저장 프로퍼티는 불확실한 상태로 둘 수 없다.
  • 이니셜라이저 안에서 저장 프로퍼티의 초기값을 설정하거나, 저장 프로퍼티를 선언할 때 기본 값을 할당해야한다. 이렇게 하면 property observer를 호출하지 않고 해당 프로퍼티의 값이 직접 설정 된다.

Default Property Values

  • 프로퍼티가 항상 동일한 초기값을 사용할 떄는 이니셜라이저 내에서 초기값을 설정하는 것보다는 기본값을 제공하는게 낫다.
  • 결과적으론 똑같지만, 기본값은 프로퍼티의 초기화를 선언과 더 밀접하게 연결한다.
  • 프로퍼티에 기본 값을 할당하는 것이 더 간결한 이니셜라이저를 만들고 기본값을 통해 프로퍼티의 타입을 유추할 수 있기때문이다.
  • 또한, 기본값을 사용하면 기본 이니셜라이저와 이니셜라이저 상속을 더 쉽게 활용할 수 있다.

Customizing Initialization

  • Initialization Parameters
    • 초기화 매개변수를 초기화를 정의할 때 사용하여 사용자가 지정하는 값의 타입과 이름을 정의할 수 있다. 함수와 메서드의 매개변수와 동일한 기능 및 구문을 갖는다.

    • 초기화에서 사용할 매개변수 이름과 호출시 사용할 인수 레이블을 모두 가질 수 있다.

    • 이니셜라이저의 매개변수 앞에는 함수처럼 이름이 없기 때문에, 이니셜라이저 매개변수의 이름과 타입은 호출해야하는 이니셜라이저를 구분하는데 중요한 역할을 한다. 이런 이유로 Swift는 초기화를 제공하지 않는 경우에 초기화의 모든 매개변수에 대해 자동 인수 레이블을 제공한다.

      struct Color {
          let red, green, blue: Double
          init(red: Double, green: Double, blue: Double) {
              self.red   = red
              self.green = green
              self.blue  = blue
          }
          init(white: Double) {
              red   = white
              green = white
              blue  = white
          }
      }
      // 구조체는 하나인데 이니셜라이저는 두개다.
      
      let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
      let halfGray = Color(white: 0.5)
      // 각각 이렇게 사용할 수 있음.
      
      let veryGreen = Color(0.0, 1.0, 0.0)  // error
      // 인수 레이블이 지정되면 호출할 때 꼭 사용해야한다. 생략하면 컴파일 타임 에러~
    • 초기화 매개변수에 인수 레이블을 사용하지 않을 때는 와일드카드(_)를 사용해 기본 동작을 재정의 할 수 있다.

  • Optional Property Types
    • 사용자 정의 타입에 "값 없음"이 논리적으로 허용되는 저장 프로퍼티가 있는 경우(초기화 중에 값을 설정해줄 수 없거나 나중에 "값 없음"이 될 수 있을 때), 옵셔널로 프로퍼티를 선언한다.

    • 옵셔널 타입의 프로퍼티는 nil 값으로 자동으로 초기화 되고, 이는 초기화 중에 프로퍼티가 의도적으로 "아직 값이 없음"으로 의도되었음을 의미한다.

      class SurveyQuestion {
          var text: String
          var response: String?  // 설문조사 응답은 질문 받을 때까지 알 수 없으므로 옵셔널!
          init(text: String) {
              self.text = text
          }
          func ask() {
              print(text)
          }
      }
      let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
      // 새 인스턴스가 초기화 될 때 nil 값이 자동으로 할당 된다.
      
      cheeseQuestion.ask()
      // Prints "Do you like cheese?"
      cheeseQuestion.response = "Yes, I do like cheese."
  • Assigning Constant Properties During Initialization
    • 초기화가 완료될 때까지 일정한 값으로 설정되어 있다면, 초기화 중 언제든지 상수 프로퍼티에 값을 할당할 수 있다. 상수 프로퍼티에 값이 할당되면, 더이상 수정 불가능.
    • 클래스 인스턴스의 경우, 상수 프로퍼티는 초기화하는 동안 해당 프로퍼티를 받은 클래스에서만 수정할 수 있다. 하위 클래스에서는 수정할 수 없다.

Default Initializers

  • Swift는 모든 프로퍼티에 대해 기본값을 제공하고 하나 이상의 초기화 자체를 제공하지 않는 모든 구조체나 클래스에 대해 default initializer를 제공한다.
  • default initializer는 모든 프로퍼티가 기본값으로 설정된 새 인스턴스를 생성한다.
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

/* ShoppingListItem 클래스의 모든 프로퍼티에 기본값이 설정되어 있고, 상위 클래스가 없는
기본 클래스이기 때문에 모든 프로퍼티가 기본값으로 설정된 새 인스턴스를 생성하는 기본 이니셜라이저를
자동으로 얻는다. name의 타입이 옵셔널 String이기때문에 이 값이 코드에 작성되지 않아도 기본값으로
nil을 할당 받는다. */
  • Memberwise Initializers for Structure Types
    • 구조체는 자체적으로 사용자 정의 이니셜라이저를 정의하지 않으면 자동으로 memberwise initializer를 받는다.

    • 기본 이니셜라이저와 다르게, 구조체는 기본값이 없는 저장 프로퍼티가 있더라도 memberwise initializer를 받는다.

    • memberwise initializer 는 새 구조체 인스턴스의 멤버 프로퍼티를 초기화하는 간단한 방법이다. 새 인스턴스의 프로퍼티에 대한 초기값은 이름으로 memberwise initializer에 전달한다.

      struct Size {
          var width = 0.0, height = 0.0     // 기본 값을 갖음.
      }
      let twoByTwo = Size(width: 2.0, height: 2.0)
      
      /* Size라는 구조체는 새 인스턴스를 초기화하는데 사용할 수 있는 멤버와이즈 이니셜라이저인 
      init(width:height:)를 자동으로 받는다. memberwise initializer를 호출할 때 
      기본값이 있는 모든 프로퍼티의 값을 생략할 수 있다. */
      
      let zeroByTwo = Size(height: 2.0)
      print(zeroByTwo.width, zeroByTwo.height)
      // Prints "0.0 2.0"
      
      let zeroByZero = Size()
      print(zeroByZero.width, zeroByZero.height)
      // Prints "0.0 0.0"
      
      // 프로퍼티 중 하나 또는 전부를 생략할 수 있고, 초기화는 생략한 모든 항목에 기본값을 사용한다.

Nested Types

  • 열거형은 특정 클래스나 구조체의 기능을 지원하기 위해 만들어지는 경우가 많다.
  • Swift는 지원하는 타입의 정의 내에서 열거형, 클래스, 구조체를 중첩 타입으로 정의해 지원할 수 있다.
  • 필요한만큼 중첩할 수 있다.
struct BlackjackCard {

    // nested Suit enumeration
    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }    // case의 rawValue가 Charater이므로, 반드시 raw type을 지정해줘야한다.

    // nested Rank enumeration
    enum Rank: Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace
        struct Values {
            let first: Int, second: Int?   // ace만 두 개의 값을 갖고있나 보당..
        }
        var values: Values {   // 구조체 타입의 연산프로퍼티 (열거형에선 저장프로퍼티X)
            switch self {   // self = Rank
            case .ace:
                return Values(first: 1, second: 11)  // Value 타입의 인스턴스 반환
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }

    // BlackjackCard properties and methods
    let rank: Rank, suit: Suit    // 구조체 BlackjackCard의 저장 프로퍼티
    var description: String {     // 구조체 BlackjackCard의 연산 프로퍼티
        var output = "suit is \(suit.rawValue),"  // 지역변수 (description안에서만)
        output += " value is \(rank.values.first)"

/* outout은 String이니까 여기서 +는 문자열 연결해주는 의미인듯.
rank는 Rank 타입인데, 그안에 구조체 Values가 있고, 그 구조체 타입의 연산 프로퍼티가 values.
이 구조체와 연산프로퍼티는 Rank라는 열거형 안에서 정의된 것이니까 rank라는 Rank타입 인스턴스에서
접근이 가능하다! 
지금까지의 output은 "suit is \(suit.rawValue), value is \(rank.values.first)" */

        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    } 
/* 만약 카드가 ace라면 옵셔널 바인딩으로 values에 second가 있는지 검사해서 output에 넣어줌.
여기까지 output은 
"suit is \(suit.rawValue), value is \(rank.values.first) or \(second)" */
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) // 인스턴스 생성
// BlackjackCard는 구조체라서 암시적 이니셜라이저를 갖고있다.

print("theAceOfSpades: \(theAceOfSpades.description)")
// Prints "theAceOfSpades: suit is ♠, value is 1 or 11"
/* Rank와 Suit가 BlackjackCard에 중첩되어있지만, 타입을 컨텍스트에서 유추할 수 있어서 
인스턴스를 초기화하면, case 이름만으로 열거 case를 참조할 수 있게 된다. */
  • Referring to Nested Types
    • 정의된 컨텍스트 외부에서 중첩 타입을 사용하려면, 해당 이름앞에 nested type의 이름을 붙이면 된다.

      let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
      // heartsSymbol is "♡"
      
      /* BlackjackCard라는 인스턴스를 만들지 않고 해당 값에 접근하려면,
      중첩 타입의 이름을 붙여서 접근하면 된다. */

참고자료

Properties - The Swift Programming Language (Swift 5.5)

프로퍼티

Swift ) Properties - Stored Property(저장 프로퍼티)

Swift ) Properties - Computed Property(연산 프로퍼티)

Swift ) Properties - Type Properties

Initialization - The Swift Programming Language (Swift 5.5)

Nested Types - The Swift Programming Language (Swift 5.5)

Swift ) Nested Types

profile
게발자🦀 되는 중.. 궁김하다.. 궁김해..

0개의 댓글