swift 기본문법 - 프로퍼티(property)와 프로퍼티 감시자(property observer)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


프로퍼티(property)

  • 저장 프로퍼티(stored property)
  • 연산 프로퍼티(computed property)
  • 인스턴스 프로퍼티(instance property)
  • 타입 프로퍼티(type property)

프로퍼티는 구조체, 클래스, 열거형 내부에 구현함으로써 타입과 연관된 값들을 표현할 떄 사용한다.

struct Student {

  // 인스턴스 저장 프로퍼티
  var name: String = ""
  var `class`: String = "Swift"
  var koreanAge: Int = 0

  // 인스턴스 연산 프로퍼티
  var westernAge: Int {

    // westernAge 값을 꺼내가려 한다면 koreanAge의 값을 역으로 환산하여 가져간다.
    get {
      return koreanAge -1
    }

    // westernAge라는 프로퍼티에 값을 셋팅하면 직접 값을 저장하는게 아니라
    // koreanAge의 프로퍼티의 값들을 연산하여 할당하거나 변환해준다.
    set(inputValue) {
      koreanAge = inputValue + 1
    }
  }

  // 타입 저장 프로퍼티: 타입과 연관되어 저장이 될 프로퍼티
  static var typeDescription: String = "학생"

  // 읽기 전용 인스턴스 연산 프로퍼티(get만 구현되어있을때)
  var selfIntroduction: String {
    get {
      return "저는 \(self.class)\(name)입니다."
    }
  }
  // 타입 메서드
  static var selfIntroduction: String {
    return "학생 타입입니다"
  }
}

// 타입 연산 프로퍼티 사용
print(Student.selfIntroduction)  // 학생 타입입니다

// 인스턴스 생성
var zehye: Student = Student()
zehye.koreanAge = 10

// 인스턴스 저장 프로퍼티 사용
zehye.name = "zehye"
print(zehye.name)  // zehye

// 인스턴스 연산 프로퍼티 사용
print(zehye.selfIntroduction)  // 저는 Swift반 zehye입니다.
print("제 한국나이는 \(zehye.koreanAge)살이고, 미쿸 나이는 \(zehye.westernAge)살 입니다.")

응용

struct Money {
    var currentcyRate: Double = 1100
    var dollar: Double = 0
    var won: Double {
        get {
            return dollar * currentcyRate
        }
        set {
            dollar = newValue / currentcyRate
        }
    }
}

var moneyInMyPocket = Money()

moneyInMyPocket.won = 110000
print(moneyInMyPocket.won)  // 110000.0
moneyInMyPocket.dollar = 10
print(moneyInMyPocket.won)  // 11000.0

저장 프로퍼티와 연산 프로퍼티의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역 변수에도 모두 사용가능하다.

var a: Int = 100
var b: Int = 200
var sum: Int {
  return a + b
}

print(sum)  // 300

프로퍼티 감시자(property observer)

프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행할 수 있다.

struct Money {
  var currentcyRate: Double = 1100 {
    willSet(newRate) {  
      print("환율이 \(currentcyRate)에서 \(newRate)으로 변경될 예정입니다")
    }
    didSet(oldRate) {
      print("환율이 \(oldRate)에서 \(currentcyRate)으로 변경되었습니다")
    }
  }
}

즉, currentcyRate 값이 변경될 때 willSet, didSet이 동작하게 된다.

  • willSet: currentcyRate가 바뀌기 직전에 동작
    • newRate: 바뀔 값이 들어옴
  • didSet: currentcyRate가 바뀌고 난 후 동작
    • oldRate: 바뀌기 이전의 값이 들어옴
var dollar: Double = 0 {
  willSet {  // willSet의 암시적 매개변수 이름 newValue
    print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
  }
  didSet {  // didSet의 암시적 매겨변수 이름 oldValue
    print("환율이 \(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
  }
}

연산 프로퍼티에서의 사용

프로퍼티 감시자와 연산 프로퍼티 기능을 동시에 사용은 불가능하다 > willSet, didSet은 저장되는 값이 변경될 때 호출되기 때문

var won: Double {
  get {
    return dollar * currentcyRate
  }
  set {
    dollar = newValue / currentcyRate
  }
}

프로퍼티 감시자의 사용

var moneyInMyPocket: Money = Money()

// 환율이 1100.0에서 1150.0으로 변경될 예정입니다
moneyInMyPocket.currentcyRate = 1150
// 환율이 1100.0에서 1150.0으로 변경되었습니다

// 0.0달러에서 10.0달러로 변경될 예정입니다
moneyInMyPocket.dollar = 10
// 0.0달러에서 10.0달러로 변경되었습니다

print(moneyInMyPocket.won)  // 11500.0

프로퍼티 감시자의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역 변수에도 모두 사용가능하다.

var a: Int = 100 {
  willSet {
    print("\(a)에서 \(newValue)로 변경될 예정입니다"
  }
  didSet {
    print("\(oldValue)에서 \(a)로 변경되었습니다")
  }
}

// 100에서 200으로 변경될 예정입니다
a = 200
// 100에서 200으로 변경되었습니다

swift 기본문법 - 클로저(Closure)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


클로저(Closure)

코드의 블럭으로 일급시민(first-citizen)이다.
일급 객체란 전달 인자로 보낼 수 있고, 변수/상수 등으로 저장하거나 전달할 수 있으며, 함수의 반환 값이 될 수도 있습니다.

따라서 변수, 상수등으로 저장, 전달인자로 전달이 가능하다. 함수의 경우 클로저의 일종으로 이름이 있는 클로저이다.

{ (매개변수 목록) -> 반환타입 in
  실행 코드
}

예시는 아래와 같다.

func sumFunction(a: Int, b:Int) -> Int {
  return a + b
}

var sumResult: Int = sumFunction(a:1, b:2)
print(sumResult)  // 3

// 위 코드를 클로저를 이용해 나타내본다.
var sum: (Int, Int) -> Int = {(a: Int, b:Int) -> Int in return a + b}
sumResult = sum(1,2)
print(sumResult)  // 3

함수는 클로저의 일종으로 sum 변수에는 당연히 함수도 할당할 수 있다.

sum = sumFunction(a:b:)

sumResult = sum(1,2)
print(sumResult)  // 3

함수의 전달인자로서의 클로저

let add: (Int, Int) -> Int
add = {(a: Int, b:Int) -> Int in return a + b}

let substract: (Int, Int) -> Int
substract = {(a:Int, b:Int) -> Int in return a - b}

let divide: (Int, Int) -> Int
divide = {(a:Int, b:Int) -> Int in return a / b}

func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int { return method(a,b ) }

var calculated: Int

calculated = calculate(a: 50, b: 20, method: add)
print(calculated)  // 70

calculated = calculate(a:50, b:20, method: substract)
print(calculated)  // 40

calculated = calculate(a:50, b:25, method: divide)
print(calculated)  // 2

calculated = calculate(a:50, b:10, method: { (left: Int, right: Int) -> Int in return left * right })
print(calculated)  // 500

클로저 고급

너무 다양한 표현법이 있기 떄문에, 적당한 축약 문법어를 사용해야 한다.

  • 후행 클로저
  • 반환타입 생략
  • 단축 인자이름
  • 암시적 반환 표현
func calculate(a:Int, b:Int, method: (Int, Int) -> Int) -> Int {
  return method(a, b)
}

method라는 이름으로 클로저를 전달받는 함수

후행 클로저

클로저가 함수의 마지막 전달인자라면 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로저를 구현할 수 있다.

result = calculate(a: 10, b:10) {(left: Int, right: Int) -> Int in return left + right }
print(result)  // 20

반환타입 생략

calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 때문에 굳이 클로저에서 반환타입을 명시해 주지 않아도 된다. 대신 in 키워드는 생략할 수 없다.

result = calculate(a:10, b:10, method: {(left: Int, right: Int) in return left + right })
print(result)  // 20

// 후행 클로저와 같이 사용 가능하다.

result2 = calculate(a:10, b:10) {(left: Int, right: Int) in return left + right }
print(result2)  // 20

단축 인자이름

클로저의 매개변수 이름이 굳이 불필요하다면 단축 인자이름을 활용할 수 있다.
단축 인자이름은 클로저의 매개변수의 순서대로 $0, $1… 처럼 사용 가능하다.

result = calculate(a: 10, b:10, method: { return $0 + $1 })
print(result)  // 20

// 후행 클로저와 같이 사용 가능하다.
result2 = calculate(a:10, b:10) {return $0 + $1}
print(result2)  // 20

암시적 반환 표현

클로저가 반환하는 값이 있다면 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급한다.

result = calculate(a: 10, b: 10) { $0 + $1 }
print(result)  // 20

swift 기본문법 - 값 타입과 참조 타입?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


Class

  • 전통적인 OOP관점에서의 클래스
  • 단일 상속
  • (인스턴스/타입) 메서드
  • (인스턴스/타입) 프로퍼티
  • 참조타입
  • Apple 프레임워크의 대부분의 큰 뼈대는 모두 클래스로 구성

Struct

  • C언어 등의 구조체보다 다양한 기능
  • 상속 불가
  • (인스턴스/타입) 메서드
  • (인스턴스/타입) 프로퍼티
  • 값 타입
  • Swift의 대부분의 큰 뼈대는 모두 구조체로 구성

Enum (Enumeration)

  • 다른 언어의 열거형과는 많이 다른 존재
  • 상속 불가
  • (인스턴스/타입) 메서드
  • (인스턴스/타입) 연산 프로퍼티
  • 값 타입
  • 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의 (요일, 상태값 월 등..)
  • 열거형 자체가 하나의 데이터타입으로 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급
  • 선언 키워드: enum

구조체는 언제사용하나?

  • 연관된 몇몇의 값들을 모아서 하나의 데이터타이으로 표현하고 싶을때
  • 다른 객체 또는 함수 등으로 전달될 때 참조가 아닌 복사를 원할때
  • 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속받을 필요가 없을 때
  • Apple 프레임워크에서 프로그래밍을 할 때에는 주로 클래스를 많이 사용

Value vs Reference?

Value: 데이터를 전달할 때 값을 복사하여 전달
Reference: 데이터를 전달할 때 값의 메모리 위치를 전달 > 단순히 참조값을 전달

code1

struct ValueType {
  var property = 1
}

class ReferenceType {
  var property = 1
}


let firstStructInstance = ValueType()
var secondStructInstance = firstStructInstance
secondStructInstance.property = 2

print("first struct instance property: \(first struct instance property)")  // 1
print("second struct instance property: \(second struct instance property)")  // 2


let firstStructInstance = Referencetype()
var secondStructInstance = firstStructInstance
secondStructInstance.property = 2

print("first struct instance property: \(first struct instance property)")  // 2
print("second struct instance property: \(second struct instance property)")  // 2

code2

struct SomeStruct {
  var someProperty: String = "Property"
}

var SomeStructInstance: SomeStruct = SomeStruct()

func someFunction(structInstance: SomeStruct) {
  var localVar: SomeStruct = structInstance
  localVar.someProperty = "ABC"
}

someFunction(SomeStructInstance)
print(SomeStructInstance.someProperty)  // Property

code3

class SomeClass {
  var someProperty: String = "Property"
}

var someClassInstace: SomeClass = SomeClass()

func someFunction(classInstance: SomeClass) {
  var localVar: SomeClass = classInstance
  locarVar.someProperty = "ABC"
}

someFunction(SomeStructInstance)
print(SomeStructInstance.someProperty)  // ABC

-> 스위프트는 구조체, 열거형 사용을 선호
-> Apple 프레임워크는 대부분 클래스 사용
-> Apple 프레임워크 사용시 그조체/클래스 선택은 각자의 몫이다

swift 기본문법 - 열거형이란?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


열거형(enum)

열거형은 타입이므로 대문자 카멜케이스를 사용하여 이름을 정의한다.

기본적으로 식별자들을 한 타입으로 사용하고 싶을때 열거형을 선언한다.

  • 각 case는 소문자 카멜케이스로 정의한다.
  • 각 case는 그 자체가 고유의 값이다.
enum 이름 {
  case 이름1
  case 이름2
  case 이름3, 이름4, 이름5
  ...
}

예시는 아래와 같다.

enum Weekday {
  case mon, tue, wed, thu, fri, sat, sun
}

// 열거형의 case를 나타내는 문법: 열거형 타입이름.case이름
var day: Weekday = Weekday.mon
day = .tru
print(day)  // tue

switch day {
case .mon, .tue, .wed, .thu:
  print("평일입니다")

case Weekday.fri:
  print("불금!")

case .sat, .sun:
  print("신나는 주말!!")
}

원시값

C언어의 enum처럼 정수값을 가지게 만들 수 있다.

  • rawValue 를 사용하면 된다.
  • rawValue는 case별로 각각 다른 값을 가져야 한다.
  • 자동으로 1이 증가된 값이 할당된다.
  • rawValue를 반드시 지닐 필요가 없다면 굳이 만들지 않아도됨
enum Fruit: Int {
  case apple = 0
  case grape = 1
  case peach
}

print("Fruit.peach.rawValue == \(Fruit.peach.rawValue)")  // 2

정수타입 뿐만 아니라 Hashable프로토콜을 따르는 모든 타입을 원시값의 타입으로 지정할 수 있다.

enum School: String {
  case elementary = "초등"
  case middle = "중등"
  case high = "고등"
  case university
}

// 열거형의 원시값 타입이 String일때, 원시값이 지정되지 않는다면 case의 이름을 원시값으로 사용
print("School.middle.rawValue == \(School.middle.rawValue)")  // 중등
print("School.university.rawValue == \(School.university.rawValue)")  // university

원시값을 통한 초기화

  • rawValue를 통해 초기화 할 수 있다.
  • rawValue가 case에 해당하지 않을 수 있으므로(3이상의 값) rawValue를 통해 초기화 한 인스턴스는 옵셔널 타입 이다.
let apple: Fruit? = Fruit(rawValue: 0)

if let orange: Fruit = Fruit(rawValue: 5) {
  print("rawValue 5에 해당하는 케이스는 \(orange)")
} else {
  print("rawValue 5에 해당하는 케이스가 없습니다")
}  // rawValue 5에 해당하는 케이스가 없습니다

열거형의 메서드

enum Month {
  case dec, jan, feb
  case mar, apr, may
  case jun, jul, aug
  case sep, act, nov

  func printMessage() {
    switch self {
      case .mar, .apr, .may {
        print("봄")
      }
      case .jun, .jul, .aug {
        print("여름")
      }
      case .sep, .oct, .nov {
        print("가을")
      }
      case .dec, .jan, .feb {
        print("겨울")
      }
    }
  }
}

Month.mar.printMessage()  // 봄

swift 기본문법 - 구조체와 클래스

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


구조체(struct)

대부분의 타입이 구조체로 이루어져있음 > 타입을 정의 > 값 타입

struct 이름 {
  구현부
}

예시는 아래와 같다.

struct Sample {
  // 인스턴스 프로퍼티
  var mutableProperty: Int = 100  // 가변 프로퍼티
  let immutableProperty: Int = 100  // 불변 프로퍼티

  // 타입 프로퍼티
  static var typeProperty: Int = 100  

  // 인스턴스 메서드
  func instanceMethod() {
    print("instance method")
  }

  // 타입 메서드
  static func typeMethod() {
    print("type method")
  }
}

프로퍼티: 구조체 안에 들어가는 인스턴스 변수
메서드: 구조체 안에 들어가는 함수

구조체 사용

// 가변 인스턴스 > 인스턴스에서 사용하는 프로퍼티
// Sample이라는 구조체가 타입이 된다.
var mutable: Sample = Sample()

mutable.mutableProperty = 200
mutable.immutableProperty = 200  // 프로퍼티 선언 자체에서 불변으로 선언한 프로퍼티의 값은 변경 불가능하다.

// 불변 인스턴스 > 인스턴스에서 사용하는 프로퍼티
let immutable: Sample = Sample()

immutable.mutableProperty = 100  // 가변 프로퍼티로 설정했다고 하더라도 불변 인스턴스의 갑은 변경 불가능하다.

// 타입 프로퍼티 및 메서드 > Sample이라는 구조체 타입 자체가 사용할 수 있는 프로퍼티, 메서드
Sample.typeProperty = 300
Sample.typeMethod()  // type method

// 인스턴스에서 타입 프로퍼티를 사용하는 것은 불가능하다.
mutable.typeProperty = 400
mutable.typeMethod()

예시는 아래와 같다.

struct Student {
   var name: String = "unknown"
   var `class`: String = "Swift"

   // 타입메서드
   static func selfIntroduce() {
     print("학생 타입입니다...")
   }

   // 인스턴스 메서드
   func selfIntroduce() {
     print("저는 \(self.class)\(name)입니다...")
   }
}

Student.selfIntroduce()  // 학생 타입입니다...

var zehye: Student = Student()
zehye.name = "zehye"
zehye.class = "스위프트"
zehye.selfIntroduce()  // 저는 스위프트반 zehye입니다...

let kina: Student = Student()  // 불변 인스턴스로 프로퍼티값 변경 불가능
kina.name = kina
kina.selfIntroduce()  // 저는 스위프트반 unknown입니다...

클래스(class)

구조체와 거의 비슷하지만 값타입인 구조체와는 다르게 클래스는 참조타입 이다.
더 나아가, 다중상속이 불가능하다.

class 이름 {
  구현부
}

예시는 아래와 같다.

class Sample {
  var mutableProperty: Int = 100
  let immutableProperty: Int = 100

  static var typeProperty: Int = 100

  func instanceMethod() {
    print("instance method")
  }

  // 타입 메서드
  // 상속을 받았을 때, 재정의 불가 타입 메서드 - static
  static func instanceMethod() {
    print("type method - static")
  }

  // 상속을 받았을 때, 재정의 가능 타입 메서드 - class
  class func classMethod() {
    print("type method - class")
  }
}

클래스

클래스는 구조체와 다르게 let, var로 인스턴스 설정하였다고 하더라도 가변 프로퍼티를 통해 값 변경이 가능하다.

var mutableReference: Sample = Sample()
let immutableReference: Sample = Sample()

mutableReference.mutableProperty = 300
immutableReference.mutableProperty = 300

// 불변 프로퍼티를 통한 값변경은 당연히 불가능하다.
mutableReference.immutableProperty = 200
immutableReference.immutableProperty = 200

// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod()

예시는 아래와 같다.

class Student {
  var name: String = "unknown"
  var `class`: String = "Swift"

  class func selfIntroduce() {
    print("학생 타입입니다...")
  }

  func selfIntroduce() {
    print("저는 \(self.class)\(name)입니다...")
  }
}


Student.selfIntroduce()  // 학생 타입입니다...

var zehye: Student = Student()
zehye.name = "지혜"
zehye.class = "스위프트"

zehye.selfIntroduce()  // 저는 스위프트반 지혜입니다...


// 구조체와 다르게 가변프로퍼티를 let으로 선언한 인스턴스의 값도 변경이 가능하다.
let kina: Student = Student()
kina.name = "키나"
kina.class = "스위프트"
kina.selfIntroduce()  // 저는 스위프트반 키나입니다...