swift 기본문법 - 옵셔널체이닝과 nil 병합 연산자(optional chaining & nil-coalescing operator)

|

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


옵셔널 체이닝(optional chaining)

구조체, 클래스등을 선언해줄때 구조체 안에 또다른 구조체 인스턴스가 들어올 수 있는데, 이렇게 연결되어 프로퍼티를 타고타고 들어가는 경우가 생긴다.
이 프로퍼티 자체가 옵셔널인 경우에는 이게 nil인지 아닌지를 확인해야하는 경우가 있다.

즉, 옵셔널 요소 내부의 프로퍼티로 또다시 옵셔널이 연속적으로 연결되는 경우 유용하게 사용할 수 있다.

class Person {
  var name: String
  var job: String?
  var home: Apartment?

  init(name: String) {
    self.name = name
  }
}

class Apartment {
  var buildingNumber: String
  var roomNumber: String
  var `guard`: Person?
  var owner: Person?

  init(dong: String, ho: String) {
    buildingNumber = dong,
    roomNumber = ho
  }
}

let zehye: Person? = Person(name: "zehye")
let apart: Apartment? = Apartment(dong: 101, ho: 3)
let superman: Person? = Person(name: "superman")

세개의 인스턴스를 생성했고 이때 생성만 하고 안에있는 프로퍼티들은 모두 nil 상태이다. > 옵셔널이 초기화됐을때 nil이 할당되어있기 때문
이때 가지게 되는것은 꼭 필요했던 프로퍼티 값들만 가지고 있다 (name, dong, ho)

옵셔널 체이닝을 사용하지않고 경비원의 직업이 궁금하다면?

func guardJob(owner: Person?) {
  if let owner = owner {
    if let home = home {
      if let `guard` = home.guard {
        if let guardJob = `guard`.job {
          print("우리집 경비원의 직업은 \(guardJob)입니다")
        } else {
          print("우리집 경비원은 직업이 없어요")
        }
      }
    }
  }
}

이렇듯 엄청 복잡하다. 이때 옵셔널 체이닝을 사용해보자!

func guardJobOptionalChaining(owner: Person?) {
  if let guardJob = owner?.home?.guard?.job {
    print("우리집 경비원의 직업은 \(guardJob)입니다")
  } else {
    print("우리집 경비원은 직업이 없어요")
  }
}

guardJobOptionalChaining(owner: zehye)

zehye?.home?.guard?.job   // nil

zehye?.home = apart
zehye?.home  // Optional(Apartment)

zehye?.home?.guard  // nil
zehye?.home?.guard = superman
zehye?.home?.guard  // Optional(Person)

zehye?.home?.guard?.name  // superman
zehye?.home?.guard?.job  // nil

zehye?.home?.guard?.job = "경비원"
zehye?.home?.guard?.job  // 경비원

nil 병합 연산자 (nil-coalescing operator)

옵셔널 체이닝을 사용하면 값이 할당되지 않은 애들은 무조건 nil을 반환하게 되는데, nil이 아닌 기본값을 설정하고 싶다면 nil 병합 연산자를 사용해준다.

이는 ?? 을 사용하는 것으로 ?? 앞의 값이 nil이라면 ?? 이후 값을 반환해달라 라는 의미이다.

var guardJob: String

guardJob = zehye?.home?.guard?.job ?? "슈퍼맨"
print(guardJob)  // 경비원

zehye?.home?.guard?.job = nil
guardJob = zehye?.home?.guard?.job ?? "슈퍼맨"
print(guardJob)  // 슈퍼맨

swift 기본문법 - 인스턴스의 생성과 소멸 (init, deinit)

|

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


인스턴스의 생성과 소멸 (init, deinit)

이전까지 클래스, 구조체를 공부했을때는 저장 프로퍼티의 값을 모두 할당해줬었다.

class PersonA {
  var name: String = "unknown"
  var age: Int = 0
  var nickName: String = "nick"
}

let zehye: PersonA = PersonA()

그 이유는 인스턴스가 생성되었을 때 해당 프로퍼티는 정상적인 값들로 초기화 되어야한다는 규칙이 있다.
그래서 기본값을 할당해놓지 않는다면 오류가 발생하게 된다. > 인스턴스가 됐을때 정상적으로 값이 들어있지 않다.

저장 프로퍼티의 초기값이 없다.

지금까지는 인스턴스를 생성한 다음에 프로퍼티에 적절한 값들을 넣어줬는데, 이는 의도와 다를 수도 있다.
초기화와 동시에 프로퍼티에 값을 할당하고 싶은데 그런 방법이 없었다.

그래서 init을 사용하면 프로퍼티 기본값을 사용하지 않더라도 실제로 인스턴스가 초기화될때 원하는 값들을 할당해줄 수 있다.

즉, 프로퍼티 기본값을 지정하기 어려울때 이니셜라이저를 통해 인스턴스가 가져야 할 초기값을 전달할 수 있다.

init(이니셜라이저)

class PersonB {
  var name: String
  var age: Int
  var nickName: String

  init(name: String, age: Int, nickName: String) {
    self.name = name
    self.age = age
    self.nickName = nickName
  }
}

let zehye: PersonB = PersonB(name: "zehye", age: 20, nickName: "지혜")

그런데 꼭 프로퍼티의 초기값이 필요하지 않을때도 있다. 그럴때 옵셔널을 사용한다!

class PersonC {
  var name: String
  var age: Int
  var nickName: String?

  init(name: String, age: Int, nickName: String) {
    self.name = name
    self.age = age
    self.nickName = nickName
  }

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

그런데 이렇게 되면 init이 너무 많아지게 된다. 중복되는 코드도 늘어나고…
이 경우에는 아래에 미리 만들어진 init을 사용하면된다. 이때 자신의 init을 사용할때 convenience를 init 앞에 붙여주면 된다.

class PersonC {
  var name: String
  var age: Int
  var nickName: String?

  convenience init(name: String, age: Int, nickName: String) {
    self.init(name: name, age: age)
    self.nickName = nickName
  }

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

// 아래와 같이 init을 다양한 모습으로 사용이 가능하다
let h: PersonC = PersonC(name: "h", age: 10)
let s: PersonC = PersonC(name: "s", age: 20, nickName: "에스")

암시적 추출 옵셔널(!)

암시적 추출 옵셔널은 인스턴스 사용에 꼭 필요하지만, 초기값을 할당하지 않고자 할 때 사용한다.

class Puppy {
  var name: String
  var owner: PersonC!

  init(name: String) {
    self.name = name
  }
}

즉, 프로퍼티가 꼭 필요한 경우에 느낌표를 찍어주는데 초기화할때 전달되기 어려운 값들의 경우

  1. name만 먼저 받아놓고
  2. 나중에 owner을 셋팅해주겠다는 의미

즉, owner값이 들어오지 않으면 런타임 오류가 발생한다. 주인을 할당해준 이후에 동작이 가능하다.

실패가능한 이니셜라이저

이니셜라이저 매개변수로 전달되는 초기값이 잘못된 경우 인스턴스생성에 실패할 수 있다.
실패하게 되면 nil을 반환 하게 되며 실패 가능한 이니셜라이저의 반환타입은 옵셔널타입 이다.

class PersonD {
  var name: String
  var age: int
  var nickName: String?

  init?(name: String, age: Int) {
    if (0...120).contains(age) == false {
      return nil
    }
    if name.characters.count == 0 {
      return nil
    }
    self.name = name
    self.age = age
  }
}

let zehye: PersonD? = PersonD(name: "zehye", age= 20)

이때 옵셔널이 아닌 타입으로 인스턴스를 생성하려고 하면 오류를 보여준다. > let zehye: PersonD = PersonD(name: "zehye", age: 20)

실패가능한 이니셜라이저이기 때문에 반환이 옵셔널로 오는데, 옵션널이 아닌 타입으로 선언을 했기 때문이다.

deinit(디이니셜라이저)

deinit은 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출된다.
즉, 인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있음을 의미한다. > 클래스 타입에만 구현해줄 수 있다.

class PersonE {
  var name: String
  var pet: Puppy?
  var child: PersonC

  init(name: String, child: PersonC ) {
    self.name = name
    self.child = child
  }

  deinit {
    if let petName = pet?.name {
      print("\(name)\(child.name)에게 \(petName)을 양도합니다.")
      self.pet?.owner = child
    }
  }
}

var donald: PersonE? = PersonE(name: "donald", child: "h")
donald.pet = happy
donald = nil  // donald가 메모리에서 해제될 시점에 deinit을 실행한다.

swift 기본문법 - 상속(Inheritance)

|

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


상속(Inheritance)

  • 스위프트에서의 상속은 클래스, 프로토콜 등에서 사용 가능
  • 열거형, 구조체에는 상속이 불가능
  • 다중상속은 지원하지 않음
class 이름: 상속받을 클래스 이름 {
  구현부
}

예시는 아래와 같다.

class Person {
  var name: String = ""

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

  // final 키워드를 사용해 자식클래스에서 재정의를 방지할 수 있다 - override 불가능
  final func sayhello() {
    print("hello")
  }

  // 타입메서드 - 재정의 불가
  static func typeMethod() {
    print("type method - static")
  }

  class func classMethod() {
    print("type method - class")
  }

  // 재정의 가능한 class 메서드지만 final 키워드를 사용하면 재정의 할 수 없다.(static = final class)
  final class func finalClassMethod() {
    print("type method - final class")
  }
}

class Student: Person {
  var major: String = ""

  oerride func selfIntroduce() {
    print("저는 \(name)이고, 전공은 \(major)입니다")
  }

  override class func classMethod() {
    print("overriden type method - class")
  }
}

Student 클래스는 Person클래스를 상속받아 온것이기 때문에 아래의 코드는 실행이 안된다.

  1. var name: String > name 변수를 재정의하는 행위
  2. override static func typeMethod() > static을 사용한 타입메서드 재정의
  3. override func sayhello() > final 키워드를 사용한 메서드나 프로퍼티 재정의
  4. oveeride class func finalClassMethod() > final class를 사용한 타입메서드 재정의
let zehye: Person = Person()
let hana: Student = Student()

zehye.name = "zehye"
hana.name = "hana"
hana.major = "Swift"

zehye.selfIntroduce()  // 저는 zehye입니다.
hana.selfIntroduce()  // 저는 hana이고, 전공은 Swift입니다.

Person.classMethod  // type method - class
Person.typeMethod()  // type method - ststic
Person.finalClassMethod()  // type method - final class

Student.classMethod()  // overriden type method - class
Student.typeMethod()  // type method - ststic
Student.finalClassMethod()  // type method - final class

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