swift 기본문법 - assert & guard, assertion, early exit

|

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


assert & guard

애플리케이션이 동작하는 도중에 생성하는 다양한 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 도와준다.

Assert

어떤 결과들을 확인해보는데 사용한다.
디버깅 모드에서만 동작 하고, 배포하는 애플리케이션에서는 동작하지 않는다.

디버깅 중에서도 예상했던 조건들이 확실히 맞는가를 확인할 때 사용한다.

var someInt: Int = 0

// someInt가 0이 맞으면 그냥 지나가고 아니면 someInt != 0 메시지를 반환
assert(someInt == 0), "someInt != 0")  

Early Exit

빠른 종료를 위해 사용한다.

디버깅 구문에서만 사용가능한 assert와는 다르게 guard는 어떤 조건에서도 동작 을 한다.

guard를 사용하여 잘못된 값이 전달됐을 시 특정 실행구문을 빠르게 종료시킨다.
guard의 else 블럭 내부 에는 특정 코드블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 한다.
타입 캐스팅, 옵셔널과도 자주 사용되며 그 외 단순 조건 판단 후 빠르게 종료할 때도 용이다.

func functionWithGuard(age: Int?) {
  guard let unwrapperdAge = age,
      unwrapperdAge < 130,
      unwrapperdAge >= 0 else {
        print("나이값 입력이 잘못되었습니다.")
        return
      }
  print("당신의 나이는 \(unwrapperdAge)세 입니다")
}

if let은 블럭 안에서만 사용가능했지만 guard let은 블럭 바깥에서도 계속 사용할 수 있다.

var count = 1
while true {
  guard count < 3 else {
    break
  }
  print(count)
  count +=1
}

// 혹은
func someFunction(info: [String: Any]) {
  guard let name = info["name"] as? String else {
    return
  }

  guard let age = info["age"] as? Int, age >= 0 else {
    return
  }

  print("\(name): \(age)")
}
  • 딕셔너리에서 나오는 값은 모두 옵셔널(key에 해당하는 값이 없기 때문)
  • info라는 딕셔너리에서 name이라는 값을 가져와 캐스팅을 시도함
    • Any 타입이기 때문에 실질적으로 사용하기 위해서는 String으로 캐스팅을 시도함

swift 기본문법 - 타입 캐스팅(type casting)

|

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


타입 캐스팅(type casting)

스위프트의 타입캐스팅은 인스턴스의 타입을 확인하는 용도로 사용한다. 뿐만 아니라 클래스의 인스턴스를, 부모 혹은 자식클래스의 타입으로 사용할 수 있는 지 확인하는 용도로 사용한다.

is, as를 사용한다.

let someInt: Int = 100
let someDouble: Double = Double(someInt)

이는 스위프트에서 타입캐스팅이 아니다. 그저 Double타입의 인스턴스를 더 생성해준 것일 뿐이다.

스위프트의 타입캐스팅은 클래스의 인스턴스에서 사용할 있다.

class Person {
  var name: String = ""
  func breath() {
    print("숨을 쉽니다")
  }
}

class Student: Person {
  var school: String = ""
  func goToSchool() {
    print("등교를 합니다")
  }
}

class UniversityStudent: Student {
  var major: String = ""
  func goToMT() {
    print("멤버쉽 트레이닝을 갑니다!")
  }
}

var zehye: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversityStudent()

타입확인 - is

var result: Bool

result = zehye is Person  // true
result = zehye is Student  // false
result = zehye is UniversityStudent  // false

result = hana is Person   // true
result = hana is Student   // true
result = hana is UniversityStudent  // false

result = jason is Person    // true
result = jason is Student   // true
result = jason is UniversityStudent   // true

업 캐스팅 - as

as를 사용하여 부모클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입정보를 전환해준다. Any 혹은 AnyObject로도 타입 정보를 변환할 수 있다. > 암시적으로 처리되므로 생략해도 무방하다

var mike: Person = UniversityStudent() as Person
var jenny: Student = Student()
var jina: Any = Person() // as Any 생략가능

var jina: UniversityStudent = Person() as UniversityStudent  // 컴파일 오류
  • Person타입이라고 하더라도 UniversityStudent의 인스턴스가 들어올 수 있다 » 왜냐면 대학생은 사람이기 때문

다운 캐스팅 - as? 또는 as!

자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입정보를 전환해준다.(사람으로 지정했는데 학생일수도 있느냐?)

  • as? 조건부 다운캐스팅
  • as! 강제 다운캐스팅

조건부 다운캐스팅 as?

var optionalCasted: Student?

optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent  // nil
optionalCasted = jina as? UniversityStudent   // nil
optionalCasted = jina as? Student  // nil
  • mike: Person 타입으로 되어잇어도 UniversityStudent로 캐스팅될 수 있다.
  • 나머지: 사람이거나 학생이었기 때문에 캐스팅이 될 수 없다.  » nil이 반환

조건부 다운캐스팅을 하게 되면 결과값이 옵셔널값이 나온다.

강제 다운캐스팅 as!

var forcedCasted: Student

optionalCasted = mike as! UniversityStudent
optionalCasted = jenny as! UniversityStudent  // 런타임 오류
optionalCasted = jina as! UniversityStudent   // 런타임 오류
optionalCasted = jina as! Student  // 런타임 오류
  • mike: 원래 대학생 타입이었기 때문에 문제없이 진행된다.
  • 나머지: 대학생이 될 수 없기 때문에 오류가 발생한다.

강제 다운캐스팅은 위험요소가 있지만 반환타입이 옵셔널이 아닌 일반 타입이기 떄문에 편하게 사용가능하다.

주로 함수로 전달되는 경우 확인해볼 수 있다.

func doSonethingWithSwitch(someone: Person) {
  switch someone {
  case is UniversityStudent:
    (someone as! UniversityStudent).goToMT()
  }
  case is Student {
    (someone as! Student).goToSchool()
  }
  case is Person {
    (someone as! Person).breath()
  }
}

switch구문을 할때는 확인만 할 뿐이지 실제 캐스팅을 하고싶다면 직접 실행을 해야한다.

그렇기 때문에 if let구문을 사용한다.  » 옵셔널 결과값을 받아와 옵셔널 추출을 한다.

func doSonethingWithSwitch(someone: Person) {
  if let universityStudent = someone as? UniversityStudent {
    universityStudent.goToMT()
  } else if let student = someone as? Student {
    student.goToSchool()
  } else if let person = someone as? Person {
    person.breath()
  }
}

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