Swift 문법

반응형

Lower Camel Case: function, method, variable, constant

Upper Camel Case: class, struct, enum, extension

 

 

/// for logging

- print

- dump: description property 까지 출력.

 

/// String Interpolation

import Swift

let age: Int = 10

print("Hello! I am \(age) years old")

print("Hello! I am \(age + 1) years old")

 

/// constant: let, variable: var

let name: type = value

var name: type = value

type 이 명확하다면 type 은 생략 가능. (type inference)

 

let sum: Int

sum = inputA + inputB

처럼 상수도 미리 name 과 type 선언 후 나중에 할당할 수 있다.

 

/// tuple

var topTitle = ("메인화면", "mainIcon.png")

topTitle.0

topTitle.1

var httpError = (404, "not found")

var httpError = (statudCode: 404, description: "not found")

httpError.statusCode

httpError.description

 

/// Basic data type: Bool, Int, UInt, Float, Double, Character, String

var someBool: Bool = true

var someInt: Int = -100

var someUInt: UInt = 100

var someFloat: Float = 3.14

var someDouble: Double = 3.14

var someCharacter: Character = "a"

var someCharacter: Character = "aaa" // error

var someString: String = "aaa" + "bbb"

someString = someCharacter // error

 

//MARK: - Any

var someAny: Any = 100

someAny = "can be any type"

someAny = 123.45

 

//MARK: - nil (see optional together)

someAny = nil

 

/// Collection type: Array, Dictionary, Set

//MARK: - Array

var integers: Array<Int> = Array<Int>()

integers.append(1)

integers.append(100)

integers.contains(100)

integers.contains(99)

integers.remove(at: 0)

integers.removeLast()

integers.removeAll()

integers.count

integers[0] // error, because now this array is empty

 

var doubles: Array<Double> = [Double]()

var strings: [String] = [String]()

var characters: [Character] = []

let immutableArray = [1, 2, 3]

// immutableArray.append(4) // error, because this array is constant so immutable

// immutableArray.removeAll() // error, because this array is constant so immutable

 

//MARK: - Dictionary

// Key 가 String 타입이고 Value 가 Any 인 빈 Dictionary 생성

var anyDictionary: Dictionary<String, Any> = [String: Any]()

anyDictionary["someKey"] = "value"

anyDictionary["anotherKey"] = 100

anyDictionary["someKey"] = "dictionary"

anyDictionary.removeValue(forKey: "anotherKey") // remove value

anyDictionary["someKey"] = nil // remove value

let emptyDictionary: [String: String] = [:]

// emptyDictionary["key"] = "value" // error, because this dictionary is constant so immutable

let initailizedDictionary: [String: String] = ["name": "yagom", "gender": "male"]

// let someValue: String = initializedDictionary["name"] // error, initializedDictionary["name"] could be nil

 

//MARK: - Set

// 빈 Int Set 생성

var integerSet: Set<Int> = Set<Int>()

integerSet.insert(1)

integerSet.insert(100)

integerSet.insert(99)

integerSet.insert(99)

integerSet.contains(1)

integerSet.contains(2)

integerSet.count

 

let setA: Set<Int> = [1, 2, 3, 4, 5]

let setB: Set<Int> = [3, 4, 5, 6, 7]

let union: Set<Int> = setA.union(setB)

let sortedUnion: [Int] = union.sorted()

let intersection: Set<Int> = setA.intersection(setB)

let subtracting: Set<Int> = setA.subtracting(setB)

 

 

/// Function

//MARK: - declare function

 

//MARK: basic form

func function_name(param1_name: param1_type, param2_name: param2_type, ...) -> return_type {

  // implementation

  // return return_value

}

func sum(a: Int, b: Int) -> Int {

  return a + b

}

 

//MARK: no return value

func function_name(param1_name: param1_type, param2_name: param2_type, ...) -> Void {

  // implementation

  // return

}

func printMyName(name: String) -> Void {

  print(name)

}

func function_name(param1_name: param1_type, param2_name: param2_type, ...) {

  // implementation

  // return

}

func printMyName(name: String) {

  print(name)

}

 

//MARK: no params

func function_name() -> return_type {

  // implementation

  // return return_value

}

func maximumIntegerValue() -> Int {

  return Int.max

}

 

//MARK: no params, no return_value

func function_name() -> Void {

  // implementation

  // return

}

func hello() -> Void { print("hello") }

func function_name() {

  // implementation

  // return

}

func bye() { print("bye") }

 

//MARK: - call function

sum(a: 3, b: 5)

printMyName(name: "Wave")

maximumIntegerValue()

hello()

bye()

//MARK: - params default value

// 기본값을 갖는 매개변수는 매개변수 목록 중에 뒤쪽에 위치하는 것이 좋다.

func greeting(friend: String, me: String = "Wave") {

  print("Hello \(friend)! I'm \(me)")

}

// 매개변수 기본값을 가지는 매개변수는 생략할 수 있습니다.

greeting(friend: "Jack") // Hello Jack! I'm Wave

greeting(friend: "Jack", me: "Hue") // Hello Jack! I'm Hugh

 

//MARK: - 전달인자 레이블

// 함수를 호출할 때 매개변수의 역할을 좀 더 명확하게 하거나 함수 사용자 입장에서 표현하고자 할 때 사용

// 함수 내부에서 전달인자를 사용할 때에는 매개변수 이름을 사용한다.

func greeting(to friend: String, from me: String) {

  print("Hello \(friend)! I'm \(me)")

}

// 함수를 호출할 때에는 전달인자 레이블을 사용해야 한다.

greeting(to: "Jack", from: "Wave") // Hello Jack! I'm Wave

// 전달인자 레이블을 사용한 함수와 사용하지 않은 함수는 다른 함수로 취급한다.

 

//MARK: - 가변 매개변수

// 전달 받을 값의 개수를 알기 어려울 때 사용할 수 있다.

// 가변 매개변수는 함수당 하나만 가질 수 있다.

func sayHelloToFriends(me: String, friends: String...) -> String {

  print("Hello \(friend)! I'm \(me)")

}

print(sayHelloToFriends(me: "Wave", friends: "Jack", "Hue", "Grantt"))

// Hello ["Jack", "Hugh", "Grantt"]! I'm Wave

print(sayHelloToFriends(me: "Wave") // 가변 매개변수에 값을 주고 싶지 않다면 생략하라.

// Hello []! I'm Wave

// print(sayHelloToFriends(me: "Wave", friends: )) // error

// print(sayHelloToFriends(me: "Wave", friends: nil)) // error

 

//MARK: - 데이터 타입으로서의 함수

// 스위프트는 함수형 프로그래밍 패러다임을 포함하는 다중 패러다임 언어이다.

// 스위프트의 함수는 일급객체이므로 변수, 상수 등에 저장이 가능하고 매개변수를 통해 전달도 가능하다.

 

//MARK: - 함수의 타입표현

// 반환타입을 생략할 수 없다.

// (param1_type: param2_type, ...) -> return_type

var someFunction: (String, String) -> Void = greeting(to:from:) // see the greeting function above

someFunction("Wave", "Jack")

someFunction = greeting(friend:me:)
someFunction = ("Wave", "Jack")

// 타입이 다른 함수는 할당할 수 없다.

// someFunction = sayHelloToFriends(me: friends:) // error

 

func runAnother(function: (String, String) -> Void) {

  function("Wave", "Jack")

}

 

runAnother(function: greeting(friend:me:))

runAnother(function: someFunction)

 

 

/// 조건문, break 없어도 break 걸린다. fallthrough 사용하면 break 안걸림.

if (someInteger < 100) {

  print("under 100")

} else if someInteger > 100 {

  print("above 100")

} else {

  print("100")

}

// 범위 연산자 사용

switch someInteger {

  case 0, 1:

    print("zero one")

    // fallthrough

  case 2..< 100:

    print("1-99")

  case 101...Int.max:

    print("over 100")

  default:

    print("unknown")

}

 

/// 반복문

for integer in integers {

  print(integer)

}

// Dictionary 의 item 은 key 와 value 로 구성된 튜블 타입이다.

for (name, age) in people {

  print("\(name): \(age)")

}

while integers.count > 1 {

  integers.removeLast()

}

// do while 과 동일. do 는 이미 예약어이기 때문에 repeat 을 사용.

repeat {

  integers.removeLast()

} while integers.count > 0

 

 

/// Optional

let optionalValue: Optional<Int> = nil

let optionalValue: Int? = nil

 

// 암시적 추출 옵셔널 (Implicitly Unwrapped Optional) (느낌표!)

var optionalValue: Int! = 100

switch optionalValue {

  case .none:

    print("This Optional variable is nil")

  case .some(let value):

    print("Value is \(value)")

}

// 기존 변수 처럼 사용 가능

optionalValue = optionalValue + 1

// nil 할당 가능

optionalValue = nil

// 잘못된 접근으로 인한 런타임 오류 발생

optionalValue = optionalValue + 1

 

// 옵셔널

var optionalValue: Int? = 100

switch optionalValue {

  case .none:

    print("This Optional variable is nil")

  case .some(let value):

    print("Value is \(value)")

}

// nil 할당 가능

optionalValue = nil

// 기존 변수처럼 사용 불가 - 옵셔널과 일반 값은 다른 타입이므로 연산 불가

optionalValue = optionalValue + 1

 

 

/// 옵셔널 값 추출

// Optional Binding, 옵셔널 값을 꺼내오는 방법 중 하나 (nil 체크 + 안전한값 추출)

func printName(_ name: String) {

  print(name)

}

var myName: String? = nil

printName(myName) // 전달되는 값의 타입이 다르기 때문에 컴파일 오류 발생

 

// if - let

if let name: String = myName {

  printName(name) // name 상수는 if-let 구문 내에서만 사용 가능.

} else {

  print("myName == nil")

}

// 상수의 사용 범위를 벗어났기 때문에 컴파일 오류 발생.

printName(name)

 

// 여러 옵셔널 변수들을 한꺼번에 바인딩을 할 수도 있다.

var myName: String? = "Wave"

var yourName: String? = nil

// yourName 이 nil 이리 때문에 실행되지 않습니다.

if let name = myName, let friend = yourName {

  print("\(name) and \(friend)")

}

yourName = "Jack"

if let name = myName, let friend = yourName {

  print("\(name) and \(friend)")

} // Wave and Jack

 

// Force Unwrapping

var myName: String? = "Wave"

printName(myName!) // Wave

myName = nil

print(myName!) // error

var yourName: String! = nil // 암시적 추출 옵셔널, 따라서 이 방식은 좋지 않다.

printName(yourName) // nil 값 전달로 인해 런타임 오류 발생

 

 

/// Unicode

let inputValue = "7"

if inputValue >- "\u{30}" && inputValue <= "\u{39}" {

  print("It is number")

} else {

  print("It is not number")

}

"\u{41}" ~ "\u{7a}" = alphabet

 

/// 구조체

// 값 타입, 상속 불가. Swifit 의 대부분의 타입이 구조체로 정의되어 있다. 

// 구조체는 초기값을 넣어주지 않아도 알아서 타입에 따라 넣어주지만 클래스는 초기값이 있어야 한다.

// Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성되어 있다.

// Apple 프레임 워크의 대부분의 큰 뼈대는 모두 클래스로 구성되어 있다.

// 연관된 몇몇의 값들을 모아서 하나의 데이터타입으로 표현하고 싶을때 사용.

// 다른 객체 또는 함수 등으로 전달될 때 참조가 아닌 복사를 원할 때 사용.

// 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속받을 필요가 없을 때 사용.

// Apple 프레임워크에서 프로그래밍할 경우 주로 클래스를 많이 사용한다.

struct name {

  // implementation

}

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")

  }

}

 

//MARK: 구조체 사용

var mutable: Sample = Sample()

mutable.mutableProperty = 200

mutable.immutableProperty = 200 // error

let immutable: Sample = Sample()

immutable.mutableProperty = 200 // error

immutable.immutableProperty = 200 // error

Sample.typeProperty = 300

Sample.typeMethod()

mutable = 400 // error

mutable.typeMethod() // error

 

struct Student {

  var name: String = "unknown"

  var `class`: String = "Swift"

  static func selfIntroduce() {

    print("학생 타입 입니다")

  }

  func selfIntroduce() {

    print("저는 \(self.class)반 \(name)입니다")

  }

}

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

var jack: Student = Student()

jack.name = "jack"

jack.class = "Swift"

jack.selfIntroduce() // 저는 Swift반 jack입니다

 

 

/// 클래스

// 구조체는 값 타입이지만 클래스는 참조 타입이다. 다중 상속이 되지 않는다.

// 클래스는 초기값이 있어야 한다. 아니면 옵셔널로 해주어야 한다.

// Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성되어 있다.

// Apple 프레임 워크의 대부분의 큰 뼈대는 모두 클래스로 구성되어 있다.

// Apple 프레임워크에서 프로그래밍할 경우 주로 클래스를 많이 사용한다.

class Sample {

  var mutableProperty: Int = 100 // 가변 프로퍼티

  let immutableProperty: Int = 100 // 불변 프로퍼티

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

 

  // 인스턴스 메서드

  func instanceMethod() {

    print("instance method")

  }

 

  // 재정의 불가 타입 메서드 - static

  static func typeMethod() {

    print("type method - static")

  }

 

  // 재정의 가능 타입 메서드 - class

  class func classMethod() {

    print("type method - class")

  }

}

 

// 클래서는 구조체와 다르게 var 나 let 모두로 선언된 경우 mutableProperty 변경이 가능하다.

var mutableReference: Sample = Sample()

mutableReference.mutableProperty = 200

let immutableReference: Sample = Sample()

immutableReference.mutableProperty = 200 // no error

 

 

/// enum, 열거형, Swift 의 열거형은 매우 강력한 기능이 있다.

// 상속 불가. 값 타입.

// 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의. (요일, 상태값, 월 등)

// 열거형 자체가 하나의 데이터 타입. 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급

// Swift 의 열거형은 자동으로 값이 할당되거나 하지 않는다.

enum 이름 {

  case 이름1

  case 이름2

  case 이름3, 이름4, 이름5

  ...

}

 

//MARK: 열거형 사용

enum Weekday {

  case mon

  case tue

  case wed

  case thu, fri, sat, sun

}

var day: Weekday = Weekday.mon

day = .tue

print(day)

 

switch day {

  case .mon, .tue, .wed, .thu:

    print("평일입니다")

  case Weekday.fri:

    print("불금입니다!")

  case .sat, .sun:

    print("신나는 주말!!")

  // 한개라도 빠져있다면 default 해주어야 한다.

}

//MARK: - 원시값, c 언어처럼 정수값을 가질 수도 있다. rawValue를 사용하면 된다. case 별로 다른 값을 가져야 한다.

enum Fruit: Int {

  case apple = 0

  case grape = 1 // c 언어처럼 자동으로 증가하기 때문에 1 지워도 1이 들어가진다.

  case peach

}

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

// Fruit.peach.rawValue == 2

// 정수 타입 뿐만 아니라

// Hashable 프로토콜을 따르는 모든 타입이 원시값의 타입으로 지정될 수 있다.

enum School: String {

  case elementary = "초등"

  case middle = "중등"

  case high = "고등"

  case university

}

print("School.university.rawValue == \(School.university.rawValue)")

// School.middle.rawValue == university

 

//MARK: 원시값을 통한 초기화

// rawValue 를 통해 초기화 할 수 있습니다

// rawValue 가 case 에 해당하지 않을 수 있으므로 rawValue 를 통해 초기화 한 인스턴스는 옵셔널 타입이다.

let apple: Fruit? = Fruit(rawValue: 0)

if let orange: Fruit =  Fruit(rawValue: 5) {

  print("rawValue 5에 해당하는 케이스는 \(orange)입니다")

} else {

  print("rawValue 5에 해당하는 케이스가 없습니다")

}

 

//MARK:  Swift enum 에는 메서드도 추가가 가능하다

enum Month {

  case dec, jan, feb

  case mar, apr, may

  case jun, jul, aug

  case sep, oct, 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()

 

//MARK: 복잡한 예시

enum BookType {

  case fiction(title: String, price: Int, year: Int)

  case comics(title: String, price: Int, year: Int)

  case workbook(title: String, price: Int, year: Int)

}

 

extension BookType {

  var typeName: String {

    switch self {

      case .comics:

        return "comics"

      case .fiction:

        return "fiction"

      case .workbook:

        return "workbook"

    }

  }

}

 

var bookStyle: BookType?

var books = [BookType]()

func save Book(book: BookType) {

  books.append(book)

}

 

saveBook(book: .comics(title: "aaa", price: 5000, year: 2020)

saveBook(book: .comics(title: "bbb", price: 6000, year: 2021)

saveBook(book: .comics(title: "ccc", price: 7000, year: 2010)

saveBook(book: .workbook(title: "ddd", price: 7000, year: 2010)

saveBook(book: .fiction(title: "eee", price: 4000, year: 2020)

saveBook(book: .fiction(title: "fff", price: 8000, year: 2015)

 

for book in books {

  if case let BookType.comics(title, _, _) = book {

    print(title, book.typeName)

  }

 

  if case let BookType.fiction(title, _, _) = book {

    print(title, book.typeName)

  }

 

  switch book {

    case let .comics(_, price, _):

      print(price)

    case let .fiction(title, _, _):

      print(title)

    default:

      break

  }

}

 

/// 클로저

// 변수, 상수 등으로 저장, 전달 인자로 전달이 가능

// 함수: 이름이 있는 클로저

 

//MARK: - 정의

{ (매개변수 목록) -> 반환타입 in 

  // 실행코드

}

// 함수를 사용한다면

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

  return a + b

}

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

// 클로저의 사용

var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in

  return a + b

}

sumResult = sum(1, 2)

print(sumResult)

// 함수는 클로저의 일종이므로 sum 변수에는 함수도 할당할 수 있다.

sum = sumFunction(a:b:)

sumResult = sum(1, 2)

print(sumResult) // 3

 

//MARK: - 함수의 전달인자로서의 클로저

let add: (Int, Int) -> Int

add = { (a: Int,  b: Int) -> Int in

  return a + b

}

let subtract: (Int, Int) -> Int

subtract = { (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 = calculated(a: 50, b: 10, method: add)

print(calculated) // 60

calculated = calculate(a: 50, b: 10, method: { (left: Int, right: Int) -> Int in

  return left * right

})

 

 

/// 클로저 고급

// 후행 클로저

// 반환타입 생략

// 단축 인자이름

// 암시적 반환 표현

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

  return method(a, b)

}

var result: Int

//MARK: - 후행 클로저

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

result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in

  return left + right

}

print(result)

//MARK: - 반환타입 생략

// calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 때문에

// 굳이 클로저에서 반환타입을 명시해 주지 않아도 된다. 대신 in 키워드는 생략할 수 없다.

result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in

  return left + right

})

print(result)

// 후행클로저와 함께 사용할 수도 있다.

result = calculate(a: 10, b: 10) { (left: Int, right: Int) in

  return left + right

}

//MARK: - 단축 인자이름

// 클로저의 매개변수 이름이 굳이 불필요하다면 단축 인자이름을 활용할 수 있다.

// 단축 인자이름은 콜러저의 매개변수의 순서대로 $0, $1, ... 처럼 표현한다.

result = calculate(a: 10, b: 10, method: {

  return $0 + $1

}

//MARK: - 암시적 반환 표현

// 클로저가 반환하는 값이 있다면

// 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급한다.

result = calculate(a: 10, b: 10) {

  $0 + $1

}

// 한 줄로 써줄 수도 있다.

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

// 축약하지 않은 클러조 문법과 축약 후의 클로저 문법 비교

result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in

  return left + right

})

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

 

 

/// 프로퍼티

// 저장 프로퍼티(stored property)

// 연산 프로퍼티(computed property -  읽기쓰기 / 읽기전용, .. 쓰기전용은 없다.)

// 인스턴스 프로퍼티(instance property)

// 타입 프로퍼티(type property)

 

// 프로퍼티는 구조체, 클래스, 열거형 내부에서 구현할 수 있다.

// 다만 열거형 내부에는 연산 프로퍼티만 구현할 수 있다.

// 연산 프로퍼티는 var 로만 선언할 수 있다.

 

//MARK: - 정의

struct Student {

  //  인스턴스 저장 프로퍼티

  var name: String = ""

  var `class`: String = "Swift"

  var koreanAge: Int = 0

  

  // 인스턴스 연산 프로퍼티

  var westernAge: Int {

    get {

      return koreanAge - 1

    }

    set(inputValue) {

      koreanAge = inputValue + 1

    }

  }

 

  // 타입 저장 프로퍼티

  static var typeDescription: String = "학생"

 

  // 인스턴스 메서드

  // func selfIntroduce() {

  //   print("저는 \(self.class)반 \(name)입니다")

  // }

  // 읽기전용 인스턴스 연산 프로퍼티

  var selfIntroduction: String {

    get {

      return "저는 \(self.class)반 \(name)입니다"

    }

  }

  // 읽기전용 타입 연산 프로퍼티, 읽기전용에서는 get 을 생략할 수 있다.

  static var selfIntroduction: String {

    return "학생타입입니다"

  }

}

struct Money {

  var currencyRate: Double = 1100

  var dollar: Double = 0

  var won: Double {

    get {

      return dollar * currencyRate

    }

    set {

      dollar = newValue / currencyRate

    }

  }

}

//MARK: - 저장 프로퍼티와 연산 프로퍼티 기능은 지역변수, 전역변수 에서도 사용가능

var a: Int = 100

var b: Int = 200

var sum: Int {

  return a + b

}

print(sum)

 

 

/// 프로퍼티 감시자

struct Money {

  var currencyRate: Double = 1100 {

    willSet(newRate) {

      print("currencyRate will be changed \(currencyRate) to \(newRate)")

    }

    didSet(oldRate) {

      print("currencyRate has changed \(oldRate) to \(currencyRate)")

    }

  }

}

 

// 프로퍼티 감사자 사용, 연산 프로퍼티 안에서는 사용할 수 없다.

var dollar: Double = 0 {

  // willSet 의 암시적 매개변수 이름 newValue

  willSet {

    print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")

  }

  // didSet 의 암시적 매개변수 이름 oldValue

  didSet {

    print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")

  }

}

// 프로퍼티 감사자는 지역변수, 전역변수 에서도 사용가능

 

 

/// 상속

class 이름: 상속받을 클래스 이름 {

  final 키워드 재정의 방지

  static 키워드 재정의 불가 타입 메서드

  class 키워드 재정의 가능 타입 메서드, final 붙이면 재정의 불가

  저장 프로퍼티는 재정의 불가 

  재정의는

  override func selfIntroduce() {

    print(...)

    super.selfIntroduce()
  }

  override class func classMethod() {}

}

 

 

/// 인스턴스의 생성과 소멸

//MARK: - 프로퍼티 기본값

// 스위프트의 모든 인스턴스는 초기화와 동시에 모든 프로퍼티에 유효한 값이 할당되어 있어야 한다.

// 프로퍼티에 미리 기본값을 할당해두면 인스턴스가 생성됨과 동시에 초기값을 지니게 된다.

//MARK: - 이니셜라이저

// 프로퍼티 기본값을 지정하기 어려운 경우에는 이니셜라이저를 통해 인스턴스가 가져야 할 초기값을 전달할 수 있다.

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 jack: PersonB = PersonB(name: "jack", age: 29, nickName: "잭")

//MARK: 프로퍼티의 초기값이 꼭 필요 없을 때

// 옵셔널을 사용

class PersonC {

  var name: String

  var age: Int

  var nickName: String?

 

  // convenience - 필수조건 사용시 다른 init 을 반드시 실행해야 한다.

  convenience init(name: String, age: Int, nickName: String) {

    // self.name = name

    // self.age = age

    self.init(name: name, age: age) // 자신의 이니셜라이저 사용시 convenience 붙여야 함.

    self.nickName = nickName

  }

 

  init(name: String, age: Int) {

    self.name = name

    self.age = age

  }

}

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

class Puppy {

  var name: String

  var owner: PersonC!

 

  init(name: String) {

    self.name = name

  }

 

  func goOUt() {

    print("\(name)가 주인 \(owner.name)와 산책을 합니다")

  }

}

//MARK: 실패가능한 이니셜라이저

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 jack: PersonD = PersonD(name: "jack", age: 23) // error

let jack: PersonD? = PersonD(name: "jack", age: 23)

let ghost: PersonD? = PersonD(name: "ghost", age: 123)

let buster: PersonD? = PersonD(name: "", age: 10)

print(ghost) // error

print(buster) // error

 

//MARK: - 디이니셜라이저

// deinit 은 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출된다.

// 인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있다.

var a: Int? = 10

a = nil

 

/// 옵셔널 체이닝 (optional chaining) 과 nil 병합 연산자 (nil-coalescing operator)

var guardJob: String

guardJob = jack?.home?.guard?.job ?? "배트맨"

 

 

/// Type Annotations

var str = "Hello, playground"

var myAge = 0 // 타입 어노테이션을 지정하지 않으면 추론에 의해 Int 형이 된다.

var screenHeight: Float = 570 // 이렇게 우측에 타입을 명확히 지정할 수 있다.

var sum = myAge + Int(screenHeight) // 엄격한 타입. 타입 다르면 + 안된다.

let guestName = "Jack"

 

 

/// 타입캐스팅

// 스위프트의 타입캐스팅은 인스턴스의 타입을 확인하는 용도

// 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용

// is, as 를 사용

let someInt: Int = 100

let someDouble: Double = Double(someInt) // 이건 Double 타입의 인스턴스를 하나 더 생성해주는 것이다.

 

Dictionary, Any, AnyObject 에서 많이 사용.

 

switch jack {

  case jack is Person:

    print("jack is Person")

  case jack is UniversityStudent:

    print("jack is UniversityStudent")

  default:

    print("nothing")

}

// 업 캐스팅, as 사용 (이건 잘 사용 안한다.)

var mike: Person = UniversityStudent() as Person

var jina: Any = Person() // as Any 생략 가능

//MARK: - 다운캐스팅 (이건 중요)

// as? 또는 as! 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입정보를 전환해준다.

// MARK: 조건부 다운캐스팅

// as?

var optionalCasted: Student?

optionalCasted = mike as? UniversityStudent

optionalCasted = jenny as? UniversityStudent // nil, if can not be casted

...

// MARK: 강제 다운 캐스팅

// as!

var forcedCated: Student

optionalCasted = mike as! UniversityStudent

optionalCated = jenny as! UniversityStudent // runtime error, if can not be casted

 

// 활용

func doSomethingWithSwitch(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()

  }

}

doSomethingWithSwitch(someone: mike as Person)

doSomethingWithSwitch(someone: mike)

doSomethingWithSwitch(someone: jenny)

func doSomethingWithSwitch(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()

  }

}

 

 

/// assert 와 guard

//MARK: - Assertion

// assert(_:_:file:line:) 함수를 사용합니다

// assert 함수는 디버깅 모드에서만 동작합니다

// 배포하는 애플리케이션에서는 제외됩니다

// 주로 디버깅 중 조건의 검증을 위하여 사용합니다

 

var someInt: Int = 0

assert(someInt == 0, "someInt != 0")

someInt = 1

// assert(someInt == 0) // 동작 중지, 검증 실패

// assert(someInt == 0, "someInt != 0") // 동작 중지, 검증 실패

// assertion failed: someInt != 0: file guard_assesrt.swift, line 22

 

//MARK: - Early Exit

// guard 를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료.

// 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작한다.

// guard 의 else 블럭 내부에는 특정 코드블럭을 종료하는 지시어(return, break 등) 가 꼭 있어야 한다.

// 타입 캐스팅, 옵셔널과도 자주 사용된다.

// 그 외 단순 조건 판단 후 빠르게 종료할 때도 용이

func functionWithGuard(age: Int?) {

  guard let unwrappedAge = age, unwrappedAge < 130, unwrappedAge >= 0 else {

    print("wrong age")

    return

  }

 

  guard unwrappedAge < 100 else {

    return

  }

 

  if unwrappedAge < 100 {

    

  } else {

    return

  }

 

  // if-let 은 블럭 안에서만 사용 가능했지만 guard-let 은 이후 로직에서 계속 적용되서 사용할 수 있다.

  print("Your age is \(unwrappedAge)")

}

 

var count = 1

while true {

  guard count < 3 else {

    break

  }

  print(count)

  count += 1

}

 

func someFunction(info: [String: Any]) {

  // 딕셔너리에서 받은 값은 대부분 옵셔널이다. 그래서 guard-let 구문을 굉장히 많이 사용한다.

  guard let name = info["name"] as? String else {

    return

  }

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

    return

  }

  print("\(name): \(age)")

}

someFunction(info: ["name": "jenny", "age": "10"])

someFunction(info: ["name": "mike"])

someFunction(info: ["name": "jack", "age": 10]) // jack: 10

 

 

/// 프로토콜

// 프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.

// 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 프로토콜의 요구사항을 실제로 구현할 수 있다.

// 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 '프로토콜을 준수한다(Confirm)'고 표현한다.

// 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 한다.

 

//MARK: - 정의 문법

protocol Talkable {

  // 프로퍼티 요구

  // 프로퍼티 요구는 항상 var 키워드를 사용

  // get 은 읽기만 가능해도 상관 없다는 뜻이며 get 과 set 을 모두 명시하면

  // 읽기 쓰기 모두 가능한 프로퍼티이어야 한다.

  var topic: String { get set }

  var language: String { get }

  

  // 메서드 요구

  func talk()

 

  // 이니셜라이저 요구

  init(topic: String, language: String)

}

 

//MARK: - 프로토콜 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택했습니다.

struct Person: Talkable {

  var topic: String

  var language: String

 

  // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능하다.

  var language: String { return "한국어" }

  // 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있다.

 

  func talk() {

    print("\(topic)에 대해 \(language)로 말합니다")

  }

 

  init(topic: String, language: String) {

    self.topic = topic

    self.language = language

  }

}

 

//MARK: - 프로토콜 상속

// 프로토콜은 클래스와 다르게 다중상속이 가능하다.

protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {

  

}

protocol Readable {

  func read()

}

protocol Writeable {

  func write()

}

protocol ReadSpeakable: Readable {

  func speak()

}

protocol ReadWriteSpeakable: Readable, Writeable {

  func speak()

}

struct SomeType: ReadWriteSpeakable {

  func read() {

    print("Read")

  }

  func write() {

    print("Write")

  }

  func speak() {

    print("Speak")

  }

}

class SubClass: SuperClass, Writeable, ReadSpeakable {

  ...

}

//MARK: - 프로토콜 준수 확인

// is, as 연산자 사용해서 확인가능

var someAnyu: Any = sup

someAny is Readable

if let someReadable: Readable = someAny as? Readable {

  someReadable.read()

}

 

 

/// 익스텐션

// 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능이다.

// 기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다해도

// 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있다.

 

// 익스텐션으로 추가할 수 있는 기능

// 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티

// 타입 메서드 / 인스턴스 메서드

// 이니셜라이저

// 서브스크립트

// 중첩 타입

// 특정 프로토콜을 준수할 수 있도록 기능 추가

// 기존에 존재하는 기능을 재정의할 수는 없다.

 

//MARK: - 정의 문법

extension 확장할 타입 이름 {

 

}

extension Int {

  var isEven: Bool {

    return self % 2 == 0

  }

  var isOdd: Bool {

    return self % 2 == 1

  }

}

print(1.isEven) // false

var number: Int = 3

print(number.isEven)

 

//MARK: 메서드 추가

extension Int {

  func multiply(by n: Int) -> Int {

    return self * n

  }

}

number = 3

print(number.multiply(by: 2))

 

//MARK: 이니셜라이저 추가

extension String {

  init(intTypeNumber: Int) {

    self = "\(intTypeNumber)"

  }

  init(doubleTypeNumber: Double) {

    self = "\(doubleTypeNumber)"

  }

}

let stringFromInt: String = String(intTypeNumber: 100)

let stringFromDouble: String = String(doubleTypeNumber: 100.0)

 

 

/// 오류처리

//MARK: - 오류표현

//Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현합니다.

enum 오류 종류 이름: Error {

  case 종류1

  case 종류2

  case 종류3

  // ...

}

 

// 자판기 동작 오류의 종류를 표현한 VendingMachineError 열거형

enum VendingMachineError: Error {

  case invalidInput

  case insufficentFunds(moneyNeeded: Int)

  case outOfStock

}

 

//MARK: - 함수에서 발생한 오류 던지기

// 자판기 동작 도중 발생한 오류 던지기

// 오류 발생의 여지가 있는 메서드는 throws 를 사용하여 오류를 내포하는 함수임을 표시한다.

class VendingMachine {

  let itemPrice: Int = 100

  var itemCount: Int = 5

  var deposited: Int = 0

 

  // 돈 받기 메서드

  func receiveMoney(_ money: Int) throws {

    // 입력한 돈이 0이하면 오류를 던진다.

    guard money > 0 else {

      throw VendingMachineError.invalidInput

    }

 

    // 오류가 없으면 정상처리를 한다.

    self.deposited += money

    print("\(money)원 받음")

  }

 

  // 물건 팔기 메서드

  func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {

    // 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던진다.

    guard numberOfItemsToVend > 0 else {

      throw VendingMachineError.invalidInput

    }

    // 구매하려는 수량보다 미리 넣어둔 돈이 적으면 오류를 던진다.

    guard numberOfItemsToVend * itemPrice <= deposited else {

      let moneyNeeded: Int

      moneyNeeded = numberOfItemsToVend * itemPrice - deposited

      throw VendingMachineError.insufficientFunds(moneyNeeded: moneyNeeded)

    }

    // 구매하려는 수량보다 요구하는 수량이 많으면 오류를 던진다.

    guard itemCount >= numberOfItemsToVend else {

      throw VendingMachineError.outOfStock

    }

    // 오류가 없으면 정상 처리

    let totalPrice = numberOfItemsToVend * itemPrice

 

    self.deposited -= totalPrice

    self.itemCount -= numberOfItemsToVend

 

    return "\(numberOfItemsToVend)개 제공함"

  }

}

// 자판기 인스턴스

let machine: VendingMachine = VendingMachine()

// 판매결과를 전달받을 변수

var result: String?

 

//MARK: - 오류처리

// 오류발생의 여지가 있는 throws 함수(메서드)는 try를 사용하여 호출해야 한다.

// try, try?, try!

 

//MARK: do-catch

// 오류발생의 여지가 있는 throws 함수(메서드)는 do-catch 구문을 활용하여 오류발생에 대비한다.

 

do {

  try machine.receiveMoney(0)

} catch VendingMachinError.invalidInput {

  print("입력이 잘못되었습니다")

} catch VendingMachinError.insufficientFunds(let moneyNeeded) {

  print("\(moneyNeeded)원이 부족합니다")

} catch VendingMachinError.outOfStock {

  print("수량이 부족합니다")

}

 

do {

  try machine.receiveMoney(300)

} catch /*(let error)*/ {

// let error 를 주석처리하면 암시적으로 error 를 사용하면 된다.

  switch error {

    case VendingMachinError.invalidInput:

      print("입력이 잘못되었습니다")

    case VendingMachinError.insufficientFunds(let moneyNeeded):

      print("\(moneyNeeded)원이 부족합니다")

    case VendingMachinError.outOfStock:

      print("수량이 부족합니다")

    default:

      print("알수없는 오류 \(error)")

  }

}

 

do {

  result = try machine.vend(numberOfItems: 4)

} catch {

  print(error)

}

do {

  try result = try machine.vend(numberOfItems: 4)

}

 

//MARK: try? 와 try!

// try?

// 별도의 오류처리 결과를 통보받지 않고 오류가 발생했으면 결과값을 nil 로 돌려받을 수 있다.

// 정상동작 후에는 옵셔널 타입으로 정상 반환값을 돌려 받는다.

result = try? machine.vend(numberOfItems: 2)

result // Optional("2개 제공함")

result = try? machine.vend(numberOfItems: 2)

result // nil

 

// try!

// 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때

// try! 를 사용하면 정상동작 후에 바로 결과값을 돌려받는다.

// 오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.

 

 

/// 고차함수 high order function

// 전달인자로 함수를 전달받거나 함수실행의 결과를 함수로 반환하는 함수

// map, filter, reduce

 

//MARK: - map

// 컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너 생성

let numbers: [Int] = [0, 1, 2, 3, 4]

var doubledNumbers: [Int]

var strings: [String]

 

//MARK: for 구문 사용

doubledNumbers = [Int]()

strings = [String]()

for number in numbers {

  doubledNumbers.append(number * 2)

  strings.append("\(number)")

}

print(doubledNumbers)

print(strings)

 

//MARK: map 메서드 사용

doubledNumbers = numbers.map({ (number: Int) -> Int in 

  return number * 2

})

doubledNumbers = numbers.map { $0 * 2 }

 

//MARK: filter 메서드 사용

let evenNumbers: [Int] = numbers.filter {

  (number: Int) -> Bool in

  return number % 2 == 0

}

print(evenNumbers)

// 매개변수, 반환 타입, 반환 키워드 생략, 후행 클로저

let oddNumbers: [Int] = numbers.filter {

  $0 * 2 != 0

}

print(oddNumbers)

 

//MARK: reduce 메서드 사용

let sum: Int = someNumbers.reduce(0, {

  (first: Int, second: Int) -> Int in

    return first + second

})

print(sum)

let sumFromThree = someNumbers.reduce(3) { $0 + $1 }

 

 

 

/// Generic

// generic <Type 내가 정한 임의의 타입>

// 로직 반복, 타입 여러가지

 

// stack

struct IntStack {

  var items = [Int]()

 

  // struct 에서는 기본적으로 값이기에 내부의 변수 변경이 불가하다. 하려면 mutating 을 붙여주어야 한다.

  mutating func push(item: Int) {

    items.append(item)

  }

 

  mutating func pop() -> Int? {

    if items.isEmpty {

      return nil

    }

    return items.removeLast()

  }

 

var myStack = IntStack()

myStack.push(item: 4)

myStack.push(item: 5)

myStack.push(item: 6)

myStack.pop()

myStack.pop()

myStack.pop()

myStack.pop()

 

// where 쓰면 타입 후보군에 제한을 줄 수 있다. Numeric, StringProtocol, Equatable, ...

struct MyStack<MyType> where MyType: Numeric {

  var items = [MyType]()

 

  // struct 에서는 기본적으로 값이기에 내부의 변수 변경이 불가하다. 하려면 mutating 을 붙여주어야 한다.

  mutating func push(item: MyType) {

    items.append(item)

  }

 

  mutating func pop() -> MyType? {

    if items.isEmpty {

      return nil

    }

    return items.removeLast()

  }

 

var myStack1 = MyStack<String>()

myStack1.push(item: "a")

myStack1.pop()

 

var myStack2 = MyStack<Int>()

myStack2.push(item: 1)

myStack2.pop()

 

var myStack3 = MyStack<[String: Any]>() // Dictionary Type, where Equatable 일 경우 Any 일 경우 오류를 낸다.

myStack2.push(item: 1)

myStack2.pop()

 

딕셔너리를 요즘 잘 사용 안한다. find,  filter 등 값을 찾아내는 경우 어려울 수 있기 때문이다. 

struct 나 class 같은 것으로 타입을 만들어 사용하는게 나을 수 있다. 이때 Equatable 을 지정해주어야 제너릭 타입에 넣어줄 때 에러가 안난다.

반응형

댓글

Designed by JB FACTORY