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()  // 저는 스위프트반 키나입니다...

swift 기본문법 - 옵셔널(Optional)

|

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


Optional

값이 ‘있을 수도, 없을 수도 있음’

let optionalConstant: Int? = nil  //혹은
let optionalConstant: Optional<Int> = nil

let someConstant: Int = nil  // 컴파일 에러 발생

옵셔널이 아닌 상수에 nil값을 할당하려고 하면 컴파일 오류가 발생한다.

Optional은 왜 필요한가?

nil의 가능성을 명시적으로 표현해주는 것

  • nil의 가능성을 문서화하지 안아도 코드만으로 충분히 표현 가능
    • 문서/주석 작성 시간을 절약
  • 전달받은 값이 옵셔널이 아니라면 nil체크를 하지 않더라도 안심하고 사용 가능
    • 효율적인 코딩이 가능해짐
    • 예외 상황을 최소화하는 안정된 코딩 가능
func someFunction(someOptionalParam: Int?) {
  // ...
}

func someFunction(someOptionalParam: Int) {
  // ...
}

someFunction(someOptionalParam: nil)
someFunction(someOptionalParam: nil)  // 컴파일 에러 발생

컴파일 단계에서도 안전하게 Optional값을 처리 가능하다.

Optional의 표현방식 > ?, !

Implicitly Unwrapped Optional, 암시적 추출 옵셔널 > !

타입 뒤에 !를 표현하는 것

옵셔널 타입 자체는 열거형이기 때문에 switch구문으로 표현이 가능하다.

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

// 잘못된 접근으로 인한 런타임 오류 발생, 이미 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 Unwrapping, 옵셔널 값 추출

옵셔널 값을 어떻게 꺼내서 쓰고 활용하는가?

  • 옵셔널 바인딩(Optional Binding) > if let
  • 강제 추출(Force Unwrapping) > !

옵셔널 바인딩(Optional Binding)

옵셔널 값을 꺼내오는 방법 중 하나로 nil체크 + 안전한 값 추출하다는 특징을 가진다.

값이 있는지 확인해보고 값이 있다면 꺼내오고 없다면 지나간다.

func printName(_ name: String) {
  print(name)
}

var myName: String? = nil

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

전달되는 값의 타입이 다를때마다 컴파일 오류가 발생하기 때문에 if-let을 통해 안전하게 옵셔널 값을 추출하도록 한다.

func printName(_ name: String) {
  print(name)
}

var myName: String! = nil

if let name: String = myName {
  printName(name)
} else {
  print("myname == nil")
}

// name이라는 상수는 if-let 구문안에서만 사용이 가능하다.
// 구문 바깥에서 사용했기 때문에 컴파일 오류가 발생한다.
printName(myName)

그리고 여러 옵셔널 변수들을 바인딩할 수 있다. 그러나 해당 변수들에 모두 값이 들어가있어야 한다.

var myName: String? = "zehye"
var youtName: String? = nil

if let name = myName, let friend = youtName {
  print("\(name) and \(friend)")  // youtName이 nil이기 때문에 실행이 안됨
}

yourName = "hana"
if let name = myName, let friend = youtName {
  print("\(name) and \(friend)")  // 실행됨
}

강제 추출(Force Unwrapping)

옵셔널의 값을 강제로 추출

func printName(_ name: String) {
  print(name)
}

var myName: String? = "zehye"
printName(myName!)  // zehye !를 사용함으로써 값을 강제로 추출

myName = nil
print(myName!)  // 강제추출 시 값이 없으므로 런타임 오류 발생

var yourName: String! = nil  // 처음 선언시 사용 가능
printName(yourName)  // 여기서 ! 사용하지 않았어도 강제추출 가능